RESTRICT AUTOMERGE: Skip time checks for key attestation am: 67de148a47
Original change: https://googleplex-android-review.googlesource.com/c/platform/cts/+/18994433
Change-Id: I6b012ef6e5f3a56dbf24b76eae080a452d4c839d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index d74516b..981f0fa 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -2942,24 +2942,6 @@
android.hardware.camera2.CaptureRequest#controlExtendedSceneMode" />
</activity>
- <activity android:name=".camera.its.CameraMuteToggleActivity"
- android:label="@string/camera_hw_toggle_test"
- android:exported="true"
- android:screenOrientation="landscape">
- <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_camera" />
- <meta-data android:name="test_required_configs" android:value="config_has_camera_toggle"/>
- <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
- <meta-data android:name="test_excluded_features"
- android:value="android.hardware.type.automotive"/>
- <meta-data android:name="display_mode"
- android:value="single_display_mode" />
- <meta-data android:name="CddTest" android:value="9.8.13/C-1-3" />
- </activity>
-
<activity android:name=".usb.accessory.UsbAccessoryTestActivity"
android:label="@string/usb_accessory_test"
android:exported="true"
@@ -5616,20 +5598,6 @@
android.hardware.usb.UsbManager#requestPermission"/>
</activity>
- <activity android:name=".audio.AudioMicrophoneMuteToggleActivity"
- android:label="@string/audio_mic_toggle_test"
- android:exported="true"
- android:screenOrientation="locked">
- <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_audio" />
- <meta-data android:name="test_required_configs" android:value="config_has_mic_toggle"/>
- <meta-data android:name="display_mode" android:value="multi_display_mode" />
- <meta-data android:name="CddTest" android:value="9.8.13/C-1-3" />
- </activity>
-
<service android:name=".tv.MockTvInputService"
android:exported="true"
android:permission="android.permission.BIND_TV_INPUT">
diff --git a/apps/CtsVerifier/res/layout/cam_hw_toggle.xml b/apps/CtsVerifier/res/layout/cam_hw_toggle.xml
deleted file mode 100644
index 95aced3..0000000
--- a/apps/CtsVerifier/res/layout/cam_hw_toggle.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- style="@style/RootLayoutPadding">
-
-<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="0dp"
- android:layout_weight="1">
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_height="fill_parent"
- android:layout_weight="3">
-
- <TextureView
- android:id="@+id/preview_view"
- android:layout_height="0dp"
- android:layout_width="fill_parent"
- android:layout_weight="3" />
- <TextView
- android:id="@+id/preview_label"
- android:layout_height="wrap_content"
- android:layout_width="fill_parent"
- android:padding="2dp"
- android:textSize="16sp"
- android:gravity="center" />
-
- </LinearLayout>
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_height="fill_parent"
- android:layout_weight="3">
-
- <ImageView
- android:id="@+id/image_view"
- android:layout_height="0dp"
- android:layout_width="fill_parent"
- android:layout_weight="3" />
- <TextView
- android:id="@+id/image_label"
- android:layout_height="wrap_content"
- android:layout_width="fill_parent"
- android:padding="2dp"
- android:textSize="16sp"
- android:gravity="center" />
-
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_height="fill_parent"
- android:layout_weight="3"
- android:gravity="bottom">
-
- <TextView
- android:id="@+id/instruction_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/camera_hw_toggle_test_instruction" />
- <Button
- android:id="@+id/take_picture_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/co_photo_button_caption" />
-
- </LinearLayout>
-
- </LinearLayout>
-
- <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
-</ScrollView>
diff --git a/apps/CtsVerifier/res/layout/mic_hw_toggle.xml b/apps/CtsVerifier/res/layout/mic_hw_toggle.xml
deleted file mode 100644
index a17abd4..0000000
--- a/apps/CtsVerifier/res/layout/mic_hw_toggle.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- style="@style/RootLayoutPadding">
-
-<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/info_text"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="3"
- android:orientation="horizontal">
- <Button
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="5"
- android:text="@string/hifi_ultrasound_test_record"
- android:id="@+id/recorder_button"/>
- </LinearLayout>
-
- <include layout="@layout/pass_fail_buttons" />
- </LinearLayout>
-</ScrollView>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index e804158..9f0aa964 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -6664,41 +6664,4 @@
<string name="report_distance_range_5m_gt_90p">Measurement Range at 5m (90th percentile)</string>
<string name="nan_precision">Nan Precision Test</string>
<string name="wifi_nan">WiFi NAN</string>
-
- <!-- Strings for AudioMicrophoneMuteToggleActivity -->
- <string name="audio_mic_toggle_test">Audio Microphone Hardware Toggle Mute Test</string>
- <string name="audio_mic_toggle_test_info">
- This test verifies that devices which implement microphone hardware privacy toggles enforce sensor privacy when toggles are enabled.
- \nTo pass the test:
- \n - <a href="https://source.android.com/compatibility/android-cdd#9813_sensorprivacymanager">The audio stream should be muted</a>.
- \n - A dialog or notification should be shown that informs the user that the sensor privacy is enabled.
- </string>
- <string name="audio_mic_toggle_test_instruction1">
- Mute the microphone using the hardware privacy toggle.
- \nPress the RECORD button.
- \nObserve a dialog with information regarding the microphone being blocked
- \nIgnore/cancel the dialog and wait for the recording to complete.
- \nThe pass button will be enabled if the test succeeded.</string>
- <string name="audio_mic_toggle_test_analyzing">Analyzing, please wait...\n</string>
-
- <!-- Strings for CameraMuteToggleActivity -->
- <string name="camera_hw_toggle_test">Camera Hardware Toggle Mute Test</string>
- <string name="camera_hw_toggle_test_info">
- This test verifies that devices which implement camera hardware privacy toggles enforce sensor privacy when toggles are enabled.
- \nTo pass the test:
- \n - <a href="https://source.android.com/compatibility/android-cdd#9813_sensorprivacymanager">The video stream should be muted</a>.
- \n - A dialog or notification should be shown that informs the user that the sensor privacy is enabled.
- </string>
- <string name="camera_hw_toggle_test_instruction">
- Mute the camera using the hardware privacy toggle.
- \nObserve a dialog with information regarding the camera being blocked.
- \nCamera preview should show a blank feed.
- \nPress the Take Photo button.
- \nCaptured image should be black.
- \nMark the test as passing if the above conditions are met.</string>
- <string name="camera_hw_toggle_test_no_camera">
- No available camera found.
- \nAdd or enable a camera and re-run this test.
- </string>
-
</resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 9393283..7bf6b5d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -19,14 +19,12 @@
import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
import static com.android.cts.verifier.TestListActivity.sInitialLaunch;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -144,10 +142,6 @@
private static final String CONFIG_QUICK_SETTINGS_SUPPORTED = "config_quick_settings_supported";
- private static final String CONFIG_HAS_MIC_TOGGLE = "config_has_mic_toggle";
-
- private static final String CONFIG_HAS_CAMERA_TOGGLE = "config_has_camera_toggle";
-
/** The config to represent that a test is only needed to run in the main display mode
* (i.e. unfolded) */
private static final String SINGLE_DISPLAY_MODE = "single_display_mode";
@@ -486,10 +480,6 @@
return false;
}
break;
- case CONFIG_HAS_MIC_TOGGLE:
- return isHardwareToggleSupported(SensorPrivacyManager.Sensors.MICROPHONE);
- case CONFIG_HAS_CAMERA_TOGGLE:
- return isHardwareToggleSupported(SensorPrivacyManager.Sensors.CAMERA);
default:
break;
}
@@ -591,16 +581,4 @@
super.loadTestResults();
}
}
-
- @SuppressLint("NewApi")
- private boolean isHardwareToggleSupported(final int sensorType) {
- boolean isToggleSupported = false;
- SensorPrivacyManager sensorPrivacyManager = mContext.getSystemService(
- SensorPrivacyManager.class);
- if (sensorPrivacyManager != null) {
- isToggleSupported = sensorPrivacyManager.supportsSensorToggle(
- SensorPrivacyManager.TOGGLE_TYPE_HARDWARE, sensorType);
- }
- return isToggleSupported;
- }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java
index 6b9bf05..48795ef 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java
@@ -17,11 +17,11 @@
package com.android.cts.verifier.audio;
import android.content.Context;
-import android.media.audiofx.AcousticEchoCanceler;
import android.media.AudioManager;
-import android.media.AudioTrack;
import android.media.AudioRecord;
+import android.media.AudioTrack;
import android.media.MediaRecorder;
+import android.media.audiofx.AcousticEchoCanceler;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@@ -32,71 +32,130 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
-import com.android.cts.verifier.audio.soundio.SoundRecorderObject;
-import com.android.cts.verifier.audio.wavelib.*;
import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
-
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+import com.android.cts.verifier.audio.wavelib.DspBufferDouble;
+import com.android.cts.verifier.audio.wavelib.DspBufferMath;
+import com.android.cts.verifier.audio.wavelib.PipeShort;
public class AudioAEC extends AudioFrequencyActivity implements View.OnClickListener {
private static final String TAG = "AudioAEC";
- // Test State
+ private static final int TEST_NONE = -1;
private static final int TEST_AEC = 0;
+ private static final int TEST_COUNT = 1;
+ private static final float MAX_VAL = (float)(1 << 15);
- // UI
+ private int mCurrentTest = TEST_NONE;
private LinearLayout mLinearLayout;
private Button mButtonTest;
private Button mButtonMandatoryYes;
private Button mButtonMandatoryNo;
private ProgressBar mProgress;
private TextView mResultTest;
+ private boolean mTestAECPassed;
+ private SoundPlayerObject mSPlayer;
+ private SoundRecorderObject mSRecorder;
+ private AcousticEchoCanceler mAec;
- // Sound IO
+ private boolean mMandatory = true;
+
private final int mBlockSizeSamples = 4096;
private final int mSamplingRate = 48000;
private final int mSelectedRecordSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
- private SoundPlayerObject mSPlayer;
- private SoundRecorderObject mSRecorder;
-
- // Test Results
- private boolean mMandatory = true;
- private boolean mTestAECPassed;
-
private final int TEST_DURATION_MS = 8000;
private final int SHOT_FREQUENCY_MS = 200;
private final int CORRELATION_DURATION_MS = TEST_DURATION_MS - 3000;
private final int SHOT_COUNT_CORRELATION = CORRELATION_DURATION_MS/SHOT_FREQUENCY_MS;
private final int SHOT_COUNT = TEST_DURATION_MS/SHOT_FREQUENCY_MS;
-
private final float MIN_RMS_DB = -60.0f; //dB
private final float MIN_RMS_VAL = (float)Math.pow(10,(MIN_RMS_DB/20));
private final double TEST_THRESHOLD_AEC_ON = 0.5;
private final double TEST_THRESHOLD_AEC_OFF = 0.6;
- private RmsHelper mRMSRecorder1 =
- new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
- private RmsHelper mRMSRecorder2 =
- new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
+ private RmsHelper mRMSRecorder1 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
+ private RmsHelper mRMSRecorder2 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
- private RmsHelper mRMSPlayer1 =
- new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
- private RmsHelper mRMSPlayer2 =
- new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
+ private RmsHelper mRMSPlayer1 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
+ private RmsHelper mRMSPlayer2 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
private Thread mTestThread;
- // ReportLog schema
- private static final String SECTION_AEC = "aec_activity";
- private static final String KEY_AEC_MANDATORY = "aec_mandatory";
- private static final String KEY_AEC_MAX_WITH = "max_with_aec";
- private static final String KEY_AEC_MAX_WITHOUT = "max_without_aec";
- private static final String KEY_AEC_RESULT = "result_string";
+ //RMS helpers
+ public class RmsHelper {
+ private double mRmsCurrent;
+ public int mBlockSize;
+ private int mShoutCount;
+ public boolean mRunning = false;
+
+ private short[] mAudioShortArray;
+
+ private DspBufferDouble mRmsSnapshots;
+ private int mShotIndex;
+
+ public RmsHelper(int blockSize, int shotCount) {
+ mBlockSize = blockSize;
+ mShoutCount = shotCount;
+ reset();
+ }
+
+ public void reset() {
+ mAudioShortArray = new short[mBlockSize];
+ mRmsSnapshots = new DspBufferDouble(mShoutCount);
+ mShotIndex = 0;
+ mRmsCurrent = 0;
+ mRunning = false;
+ }
+
+ public void captureShot() {
+ if (mShotIndex >= 0 && mShotIndex < mRmsSnapshots.getSize()) {
+ mRmsSnapshots.setValue(mShotIndex++, mRmsCurrent);
+ }
+ }
+
+ public void setRunning(boolean running) {
+ mRunning = running;
+ }
+
+ public double getRmsCurrent() {
+ return mRmsCurrent;
+ }
+
+ public DspBufferDouble getRmsSnapshots() {
+ return mRmsSnapshots;
+ }
+
+ public boolean updateRms(PipeShort pipe, int channelCount, int channel) {
+ if (mRunning) {
+ int samplesAvailable = pipe.availableToRead();
+ while (samplesAvailable >= mBlockSize) {
+ pipe.read(mAudioShortArray, 0, mBlockSize);
+
+ double rmsTempSum = 0;
+ int count = 0;
+ for (int i = channel; i < mBlockSize; i += channelCount) {
+ float value = mAudioShortArray[i] / MAX_VAL;
+
+ rmsTempSum += value * value;
+ count++;
+ }
+ float rms = count > 0 ? (float)Math.sqrt(rmsTempSum / count) : 0f;
+ if (rms < MIN_RMS_VAL) {
+ rms = MIN_RMS_VAL;
+ }
+
+ double alpha = 0.9;
+ double total_rms = rms * alpha + mRmsCurrent * (1.0f - alpha);
+ mRmsCurrent = total_rms;
+
+ samplesAvailable = pipe.availableToRead();
+ }
+ return true;
+ }
+ return false;
+ }
+ }
//compute Acoustic Coupling Factor
private double computeAcousticCouplingFactor(DspBufferDouble buffRmsPlayer,
@@ -143,7 +202,7 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.audio_aec_activity);
- // "AEC Mandatory" Buttons
+ //
mLinearLayout = (LinearLayout)findViewById(R.id.audio_aec_test_layout);
mButtonMandatoryYes = (Button) findViewById(R.id.audio_aec_mandatory_yes);
mButtonMandatoryYes.setOnClickListener(this);
@@ -151,15 +210,16 @@
mButtonMandatoryNo.setOnClickListener(this);
enableUILayout(mLinearLayout, false);
- // Test Button
+ // Test
mButtonTest = (Button) findViewById(R.id.audio_aec_button_test);
mButtonTest.setOnClickListener(this);
mProgress = (ProgressBar) findViewById(R.id.audio_aec_test_progress_bar);
mResultTest = (TextView) findViewById(R.id.audio_aec_test_result);
- showProgressIndicator(false);
+ showView(mProgress, false);
mSPlayer = new SoundPlayerObject(false, mBlockSizeSamples) {
+
@Override
public void periodicNotification(AudioTrack track) {
int channelCount = getChannelCount();
@@ -179,11 +239,12 @@
setPassFailButtonClickListeners();
getPassButton().setEnabled(false);
- setInfoResources(R.string.audio_aec_test, R.string.audio_aec_info, -1);
+ setInfoResources(R.string.audio_aec_test,
+ R.string.audio_aec_info, -1);
}
- private void showProgressIndicator(boolean show) {
- mProgress.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+ private void showView(View v, boolean show) {
+ v.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
@Override
@@ -213,11 +274,7 @@
Log.v(TAG,"test Thread already running.");
return;
}
-
mTestThread = new Thread(new AudioTestRunner(TAG, TEST_AEC, mMessageHandler) {
- // AcousticEchoCanceler
- private AcousticEchoCanceler mAec;
-
public void run() {
super.run();
@@ -433,39 +490,28 @@
String msg) {
CtsVerifierReportLog reportLog = getReportLog();
- reportLog.addValue(KEY_AEC_MANDATORY,
+ reportLog.addValue("AEC_mandatory",
aecMandatory,
ResultType.NEUTRAL,
ResultUnit.NONE);
- reportLog.addValue(KEY_AEC_MAX_WITH,
+ reportLog.addValue("max_with_AEC",
maxAEC,
ResultType.LOWER_BETTER,
ResultUnit.SCORE);
- reportLog.addValue(KEY_AEC_MAX_WITHOUT,
+ reportLog.addValue("max_without_AEC",
maxNoAEC,
ResultType.HIGHER_BETTER,
ResultUnit.SCORE);
- reportLog.addValue(KEY_AEC_RESULT,
+ reportLog.addValue("result_string",
msg,
ResultType.NEUTRAL,
ResultUnit.NONE);
}
- //
- // PassFailButtons Overrides
- //
- @Override
- public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
-
- @Override
- public final String getReportSectionName() {
- return setTestNameSuffix(sCurrentDisplayMode, SECTION_AEC);
- }
-
- @Override
+ @Override // PassFailButtons
public void recordTestResults() {
getReportLog().submit();
}
@@ -477,7 +523,7 @@
public void testStarted(int testId, String str) {
super.testStarted(testId, str);
Log.v(TAG, "Test Started! " + testId + " str:"+str);
- showProgressIndicator(true);
+ showView(mProgress, true);
mTestAECPassed = false;
getPassButton().setEnabled(false);
mResultTest.setText("test in progress..");
@@ -494,7 +540,7 @@
public void testEndedOk(int testId, String str) {
super.testEndedOk(testId, str);
Log.v(TAG, "Test EndedOk. " + testId + " str:"+str);
- showProgressIndicator(false);
+ showView(mProgress, false);
mResultTest.setText("test completed. " + str);
if (mTestAECPassed) {
getPassButton().setEnabled(true);;
@@ -505,7 +551,7 @@
public void testEndedError(int testId, String str) {
super.testEndedError(testId, str);
Log.v(TAG, "Test EndedError. " + testId + " str:"+str);
- showProgressIndicator(false);
+ showView(mProgress, false);
mResultTest.setText("test failed. " + str);
}
};
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
index 9b24039..582ea0c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
@@ -32,7 +32,6 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
import com.android.cts.verifier.CtsVerifierReportLog;
import com.android.cts.verifier.R;
import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
index e066943..a83709d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
@@ -40,9 +40,6 @@
import com.android.cts.verifier.audio.wavelib.DspFftServer;
import com.android.cts.verifier.audio.wavelib.DspWindow;
import com.android.cts.verifier.audio.wavelib.PipeShort;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
-import com.android.cts.verifier.audio.wavelib.*;
-
import com.android.cts.verifier.audio.wavelib.VectorAverage;
/**
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
index 0e63b6f..301eb9c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
@@ -32,7 +32,6 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
import com.android.cts.verifier.CtsVerifierReportLog;
import com.android.cts.verifier.R;
import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
index 21de117..3819be2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
@@ -34,7 +34,6 @@
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
import com.android.cts.verifier.CtsVerifierReportLog;
import com.android.cts.verifier.R;
import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
index 442f626..23a016a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
@@ -30,8 +30,6 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
-import com.android.cts.verifier.audio.soundio.SoundRecorderObject;
import com.android.cts.verifier.CtsVerifierReportLog;
import com.android.cts.verifier.R;
import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java
deleted file mode 100644
index 1e250ef..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio;
-
-import android.media.MediaRecorder;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.text.method.ScrollingMovementMethod;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
-
-/**
- * Test for manual verification of microphone privacy hardware switches
- */
-public class AudioMicrophoneMuteToggleActivity extends PassFailButtons.Activity {
-
- public enum Status {
- START, RECORDING, DONE, PLAYER
- }
-
- private static final String TAG = "AudioMicrophoneMuteToggleActivity";
-
- private Status mStatus = Status.START;
- private TextView mInfoText;
- private Button mRecorderButton;
-
- private int mAudioSource = -1;
- private int mRecordRate = 0;
-
- // keys for report log
- private static final String KEY_REC_RATE = "rec_rate";
- private static final String KEY_AUDIO_SOURCE = "audio_source";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.mic_hw_toggle);
- setInfoResources(R.string.audio_mic_toggle_test, R.string.audio_mic_toggle_test_info, -1);
- setPassFailButtonClickListeners();
- getPassButton().setEnabled(false);
-
- mInfoText = findViewById(R.id.info_text);
- mInfoText.setMovementMethod(new ScrollingMovementMethod());
- mInfoText.setText(R.string.audio_mic_toggle_test_instruction1);
-
- mRecorderButton = findViewById(R.id.recorder_button);
- mRecorderButton.setEnabled(true);
-
- final AudioRecordHelper audioRecorder = AudioRecordHelper.getInstance();
- mRecordRate = audioRecorder.getSampleRate();
-
- mRecorderButton.setOnClickListener(new View.OnClickListener() {
- private WavAnalyzerTask mWavAnalyzerTask = null;
-
- private void stopRecording() {
- audioRecorder.stop();
- mInfoText.append(getString(R.string.audio_mic_toggle_test_analyzing));
- mWavAnalyzerTask = new WavAnalyzerTask(audioRecorder.getByte());
- mWavAnalyzerTask.execute();
- mStatus = Status.DONE;
- }
-
- @Override
- public void onClick(View v) {
- switch (mStatus) {
- case START:
- mInfoText.append("Recording at " + mRecordRate + "Hz using ");
- mAudioSource = audioRecorder.getAudioSource();
- switch (mAudioSource) {
- case MediaRecorder.AudioSource.MIC:
- mInfoText.append("MIC");
- break;
- case MediaRecorder.AudioSource.VOICE_RECOGNITION:
- mInfoText.append("VOICE_RECOGNITION");
- break;
- default:
- mInfoText.append("UNEXPECTED " + mAudioSource);
- break;
- }
- mInfoText.append("\n");
- mStatus = Status.RECORDING;
-
- mRecorderButton.setEnabled(false);
- audioRecorder.start();
-
- final View finalV = v;
- new Thread() {
- @Override
- public void run() {
- double recordingDuration_millis = (1000 * (2.5
- + AudioCommon.PREFIX_LENGTH_S
- + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
- + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
- + AudioCommon.PIP_NUM * (AudioCommon.PIP_DURATION_S
- + AudioCommon.PAUSE_DURATION_S)
- * AudioCommon.REPETITIONS));
- Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
- try {
- Thread.sleep((long) recordingDuration_millis);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- stopRecording();
- }
- });
- }
- }.start();
-
- break;
-
- default:
- break;
- }
- }
- });
-
- }
-
- @Override
- public void recordTestResults() {
- CtsVerifierReportLog reportLog = getReportLog();
-
- reportLog.addValue(
- KEY_REC_RATE,
- mRecordRate,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_AUDIO_SOURCE,
- mAudioSource,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.submit();
- }
-
- /**
- * AsyncTask class for the analyzing.
- */
- private class WavAnalyzerTask extends AsyncTask<Void, String, String>
- implements WavAnalyzer.Listener {
-
- private static final String TAG = "WavAnalyzerTask";
- private final WavAnalyzer mWavAnalyzer;
-
- public WavAnalyzerTask(byte[] recording) {
- mWavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
- WavAnalyzerTask.this);
- }
-
- @Override
- protected String doInBackground(Void... params) {
- boolean result = mWavAnalyzer.doWork();
- if (result) {
- return getString(R.string.pass_button_text);
- }
- return getString(R.string.fail_button_text);
- }
-
- @Override
- protected void onPostExecute(String result) {
- if (mWavAnalyzer.isSilence()) {
- mInfoText.append(getString(R.string.passed));
- getPassButton().setEnabled(true);
- } else {
- mInfoText.append(getString(R.string.failed));
- }
- }
-
- @Override
- protected void onProgressUpdate(String... values) {
- for (String message : values) {
- Log.d(TAG, message);
- }
- }
-
- @Override
- public void sendMessage(String message) {
- publishProgress(message);
- }
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioCommon.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/Common.java
similarity index 90%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioCommon.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/Common.java
index ba5e39b2..df7460a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioCommon.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/Common.java
@@ -1,18 +1,15 @@
-package com.android.cts.verifier.audio.audiolib;
+package com.android.cts.verifier.audio;
import android.media.AudioManager;
import android.media.AudioTrack;
-import com.android.cts.verifier.audio.AudioRecordHelper;
-import com.android.cts.verifier.audio.Util;
-
import java.util.ArrayList;
import java.util.Random;
/**
* This class stores common constants and methods.
*/
-public class AudioCommon {
+public class Common {
public static final int RECORDING_SAMPLE_RATE_HZ
= AudioRecordHelper.getInstance().getSampleRate();
@@ -98,7 +95,7 @@
private static double[] frequencies() {
double[] originalFrequencies = originalFrequencies();
- double[] randomFrequencies = new double[AudioCommon.REPETITIONS * originalFrequencies.length];
+ double[] randomFrequencies = new double[Common.REPETITIONS * originalFrequencies.length];
for (int i = 0; i < REPETITIONS * originalFrequencies.length; i++) {
randomFrequencies[i] = originalFrequencies[ORDER[i] % originalFrequencies.length];
}
@@ -111,13 +108,13 @@
*/
private static double[] originalFrequencies() {
ArrayList<Double> frequencies = new ArrayList<Double>();
- double frequency = AudioCommon.MIN_FREQUENCY_HZ;
- while (frequency <= AudioCommon.MAX_FREQUENCY_HZ) {
+ double frequency = Common.MIN_FREQUENCY_HZ;
+ while (frequency <= Common.MAX_FREQUENCY_HZ) {
frequencies.add(new Double(frequency));
if ((frequency >= 18500) && (frequency < 20000)) {
- frequency += AudioCommon.FREQUENCY_STEP_HZ;
+ frequency += Common.FREQUENCY_STEP_HZ;
} else {
- frequency += AudioCommon.FREQUENCY_STEP_HZ * 10;
+ frequency += Common.FREQUENCY_STEP_HZ * 10;
}
}
Double[] frequenciesArray = frequencies.toArray(new Double[frequencies.size()]);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
index b071652..3dccf1f1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
@@ -37,10 +37,6 @@
import android.widget.TextView;
import java.util.Arrays;
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.soundio.SoundGenerator;
-import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
-
import com.androidplot.xy.PointLabelFormatter;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
@@ -185,12 +181,11 @@
@Override
public void run() {
Double recordingDuration_millis = new Double(1000 * (2.5
- + AudioCommon.PREFIX_LENGTH_S
- + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
- + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
- + AudioCommon.PIP_NUM
- * (AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S)
- * AudioCommon.REPETITIONS));
+ + Common.PREFIX_LENGTH_S
+ + Common.PAUSE_BEFORE_PREFIX_DURATION_S
+ + Common.PAUSE_AFTER_PREFIX_DURATION_S
+ + Common.PIP_NUM * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S)
+ * Common.REPETITIONS));
Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
try {
Thread.sleep(recordingDuration_millis.intValue());
@@ -267,17 +262,17 @@
XYPlot plot = (XYPlot) popupView.findViewById(R.id.responseChart);
plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 2000);
- Double[] frequencies = new Double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
- frequencies[i] = new Double(AudioCommon.FREQUENCIES_ORIGINAL[i]);
+ Double[] frequencies = new Double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
+ frequencies[i] = new Double(Common.FREQUENCIES_ORIGINAL[i]);
}
if (wavAnalyzerTask != null) {
double[][] power = wavAnalyzerTask.getPower();
- for(int i = 0; i < AudioCommon.REPETITIONS; i++) {
- Double[] powerWrap = new Double[AudioCommon.PIP_NUM];
- for (int j = 0; j < AudioCommon.PIP_NUM; j++) {
+ for(int i = 0; i < Common.REPETITIONS; i++) {
+ Double[] powerWrap = new Double[Common.PIP_NUM];
+ for (int j = 0; j < Common.PIP_NUM; j++) {
powerWrap[j] = new Double(10 * Math.log10(power[j][i]));
}
XYSeries series = new SimpleXYSeries(
@@ -292,8 +287,8 @@
}
double[] noiseDB = wavAnalyzerTask.getNoiseDB();
- Double[] noiseDBWrap = new Double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+ Double[] noiseDBWrap = new Double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
noiseDBWrap[i] = new Double(noiseDB[i]);
}
@@ -308,8 +303,8 @@
plot.addSeries(noiseSeries, noiseSeriesFormat);
double[] dB = wavAnalyzerTask.getDB();
- Double[] dBWrap = new Double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+ Double[] dBWrap = new Double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
dBWrap[i] = new Double(dB[i]);
}
@@ -323,7 +318,7 @@
R.xml.ultrasound_line_formatter_median);
plot.addSeries(series, seriesFormat);
- Double[] passX = new Double[] {AudioCommon.MIN_FREQUENCY_HZ, AudioCommon.MAX_FREQUENCY_HZ};
+ Double[] passX = new Double[] {Common.MIN_FREQUENCY_HZ, Common.MAX_FREQUENCY_HZ};
Double[] passY = new Double[] {wavAnalyzerTask.getThreshold(), wavAnalyzerTask.getThreshold()};
XYSeries passSeries = new SimpleXYSeries(
Arrays.asList(passX), Arrays.asList(passY), "passing");
@@ -339,7 +334,7 @@
* Plays the generated pips.
*/
private void play() {
- play(SoundGenerator.getInstance().getByte(), AudioCommon.PLAYING_SAMPLE_RATE_HZ);
+ play(SoundGenerator.getInstance().getByte(), Common.PLAYING_SAMPLE_RATE_HZ);
}
/**
@@ -369,7 +364,7 @@
WavAnalyzer wavAnalyzer;
public WavAnalyzerTask(byte[] recording) {
- wavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
+ wavAnalyzer = new WavAnalyzer(recording, Common.RECORDING_SAMPLE_RATE_HZ,
WavAnalyzerTask.this);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
index 2948e38..f5e4271 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
@@ -37,10 +37,6 @@
import android.widget.TextView;
import java.util.Arrays;
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.soundio.SoundGenerator;
-import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
-
import com.androidplot.xy.PointLabelFormatter;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
@@ -165,11 +161,11 @@
@Override
public void run() {
Double recordingDuration_millis = new Double(1000 * (2.5
- + AudioCommon.PREFIX_LENGTH_S
- + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
- + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
- + AudioCommon.PIP_NUM * (AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S)
- * AudioCommon.REPETITIONS));
+ + Common.PREFIX_LENGTH_S
+ + Common.PAUSE_BEFORE_PREFIX_DURATION_S
+ + Common.PAUSE_AFTER_PREFIX_DURATION_S
+ + Common.PIP_NUM * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S)
+ * Common.REPETITIONS));
Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
try {
Thread.sleep(recordingDuration_millis.intValue());
@@ -225,18 +221,18 @@
XYPlot plot = (XYPlot) popupView.findViewById(R.id.responseChart);
plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 2000);
- Double[] frequencies = new Double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
- frequencies[i] = new Double(AudioCommon.FREQUENCIES_ORIGINAL[i]);
+ Double[] frequencies = new Double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
+ frequencies[i] = new Double(Common.FREQUENCIES_ORIGINAL[i]);
}
if (wavAnalyzerTask != null && wavAnalyzerTask.getPower() != null &&
wavAnalyzerTask.getNoiseDB() != null && wavAnalyzerTask.getDB() != null) {
double[][] power = wavAnalyzerTask.getPower();
- for(int i = 0; i < AudioCommon.REPETITIONS; i++) {
- Double[] powerWrap = new Double[AudioCommon.PIP_NUM];
- for (int j = 0; j < AudioCommon.PIP_NUM; j++) {
+ for(int i = 0; i < Common.REPETITIONS; i++) {
+ Double[] powerWrap = new Double[Common.PIP_NUM];
+ for (int j = 0; j < Common.PIP_NUM; j++) {
powerWrap[j] = new Double(10 * Math.log10(power[j][i]));
}
XYSeries series = new SimpleXYSeries(
@@ -251,8 +247,8 @@
}
double[] noiseDB = wavAnalyzerTask.getNoiseDB();
- Double[] noiseDBWrap = new Double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+ Double[] noiseDBWrap = new Double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
noiseDBWrap[i] = new Double(noiseDB[i]);
}
@@ -267,8 +263,8 @@
plot.addSeries(noiseSeries, noiseSeriesFormat);
double[] dB = wavAnalyzerTask.getDB();
- Double[] dBWrap = new Double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+ Double[] dBWrap = new Double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
dBWrap[i] = new Double(dB[i]);
}
@@ -282,7 +278,7 @@
R.xml.ultrasound_line_formatter_median);
plot.addSeries(series, seriesFormat);
- Double[] passX = new Double[] {AudioCommon.MIN_FREQUENCY_HZ, AudioCommon.MAX_FREQUENCY_HZ};
+ Double[] passX = new Double[] {Common.MIN_FREQUENCY_HZ, Common.MAX_FREQUENCY_HZ};
Double[] passY = new Double[] {wavAnalyzerTask.getThreshold(), wavAnalyzerTask.getThreshold()};
XYSeries passSeries = new SimpleXYSeries(
Arrays.asList(passX), Arrays.asList(passY), "passing");
@@ -298,7 +294,7 @@
* Plays the generated pips.
*/
private void play() {
- play(SoundGenerator.getInstance().getByte(), AudioCommon.PLAYING_SAMPLE_RATE_HZ);
+ play(SoundGenerator.getInstance().getByte(), Common.PLAYING_SAMPLE_RATE_HZ);
}
/**
@@ -328,7 +324,7 @@
WavAnalyzer wavAnalyzer;
public WavAnalyzerTask(byte[] recording) {
- wavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
+ wavAnalyzer = new WavAnalyzer(recording, Common.RECORDING_SAMPLE_RATE_HZ,
WavAnalyzerTask.this);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundGenerator.java
new file mode 100644
index 0000000..0ad9371
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundGenerator.java
@@ -0,0 +1,81 @@
+package com.android.cts.verifier.audio;
+
+/**
+ * Sound generator.
+ */
+public class SoundGenerator {
+
+ private static SoundGenerator instance;
+
+ private final byte[] generatedSound;
+ private final double[] sample;
+
+ private SoundGenerator() {
+ // Initialize sample.
+ int pipNum = Common.PIP_NUM;
+ int prefixTotalLength = Util.toLength(Common.PREFIX_LENGTH_S, Common.PLAYING_SAMPLE_RATE_HZ)
+ + Util.toLength(Common.PAUSE_BEFORE_PREFIX_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ)
+ + Util.toLength(Common.PAUSE_AFTER_PREFIX_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ);
+ int repetitionLength = pipNum * Util.toLength(
+ Common.PIP_DURATION_S + Common.PAUSE_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ);
+ int sampleLength = prefixTotalLength + Common.REPETITIONS * repetitionLength;
+ sample = new double[sampleLength];
+
+ // Fill sample with prefix.
+ System.arraycopy(Common.PREFIX_FOR_PLAYER, 0, sample,
+ Util.toLength(Common.PAUSE_BEFORE_PREFIX_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ),
+ Common.PREFIX_FOR_PLAYER.length);
+
+ // Fill the sample.
+ for (int i = 0; i < pipNum * Common.REPETITIONS; i++) {
+ double[] pip = getPip(Common.WINDOW_FOR_PLAYER, Common.FREQUENCIES[i]);
+ System.arraycopy(pip, 0, sample,
+ prefixTotalLength + i * Util.toLength(
+ Common.PIP_DURATION_S + Common.PAUSE_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ),
+ pip.length);
+ }
+
+ // Convert sample to byte.
+ generatedSound = new byte[2 * sample.length];
+ int i = 0;
+ for (double dVal : sample) {
+ short val = (short) ((dVal * 32767));
+ generatedSound[i++] = (byte) (val & 0x00ff);
+ generatedSound[i++] = (byte) ((val & 0xff00) >>> 8);
+ }
+ }
+
+ public static SoundGenerator getInstance() {
+ if (instance == null) {
+ instance = new SoundGenerator();
+ }
+ return instance;
+ }
+
+ /**
+ * Gets a pip sample.
+ */
+ private static double[] getPip(double[] window, double frequency) {
+ int pipArrayLength = window.length;
+ double[] pipArray = new double[pipArrayLength];
+ double radPerSample = 2 * Math.PI / (Common.PLAYING_SAMPLE_RATE_HZ / frequency);
+ for (int i = 0; i < pipArrayLength; i++) {
+ pipArray[i] = window[i] * Math.sin(i * radPerSample);
+ }
+ return pipArray;
+ }
+
+ /**
+ * Get generated sound in byte[].
+ */
+ public byte[] getByte() {
+ return generatedSound;
+ }
+
+ /**
+ * Get sample in double[].
+ */
+ public double[] getSample() {
+ return sample;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundPlayerObject.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundPlayerObject.java
similarity index 99%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundPlayerObject.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundPlayerObject.java
index 156460b..0d93dbb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundPlayerObject.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundPlayerObject.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.verifier.audio.soundio;
+package com.android.cts.verifier.audio;
import android.content.Context;
import android.media.AudioAttributes;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundRecorderObject.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundRecorderObject.java
similarity index 98%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundRecorderObject.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundRecorderObject.java
index d83a5dd..8950ec5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundRecorderObject.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundRecorderObject.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.verifier.audio.soundio;
+package com.android.cts.verifier.audio;
import android.media.AudioFormat;
import android.media.AudioRecord;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/WavAnalyzer.java
similarity index 62%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/WavAnalyzer.java
index 038f080..b75c40b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/WavAnalyzer.java
@@ -1,22 +1,4 @@
-/*
- * 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.verifier.audio.wavelib;
-
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.Util;
+package com.android.cts.verifier.audio;
import org.apache.commons.math.complex.Complex;
@@ -27,8 +9,6 @@
* Class contains the analysis to calculate frequency response.
*/
public class WavAnalyzer {
- final double SILENCE_THRESHOLD = Short.MAX_VALUE / 100.0f;
-
private final Listener listener;
private final int sampleRate; // Recording sampling rate.
private double[] data; // Whole recording data.
@@ -80,7 +60,7 @@
/**
* Check if the recording is clipped.
*/
- public boolean isClipped() {
+ boolean isClipped() {
for (int i = 1; i < data.length; i++) {
if ((Math.abs(data[i]) >= Short.MAX_VALUE) && (Math.abs(data[i - 1]) >= Short.MAX_VALUE)) {
listener.sendMessage("WARNING: Data is clipped."
@@ -94,11 +74,11 @@
/**
* Check if the result is consistant across trials.
*/
- public boolean isConsistent() {
- double[] coeffOfVar = new double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
- double[] powerAtFreq = new double[AudioCommon.REPETITIONS];
- for (int j = 0; j < AudioCommon.REPETITIONS; j++) {
+ boolean isConsistent() {
+ double[] coeffOfVar = new double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
+ double[] powerAtFreq = new double[Common.REPETITIONS];
+ for (int j = 0; j < Common.REPETITIONS; j++) {
powerAtFreq[j] = power[i][j];
}
coeffOfVar[i] = Util.std(powerAtFreq) / Util.mean(powerAtFreq);
@@ -114,7 +94,7 @@
/**
* Determine test pass/fail using the frequency response. Package visible for unit testing.
*/
- public boolean responsePassesHifiTest(double[] dB) {
+ boolean responsePassesHifiTest(double[] dB) {
for (int i = 0; i < dB.length; i++) {
// Precautionary; NaN should not happen.
if (Double.isNaN(dB[i])) {
@@ -124,16 +104,16 @@
}
}
- if (Util.mean(dB) - Util.mean(noiseDB) < AudioCommon.SIGNAL_MIN_STRENGTH_DB_ABOVE_NOISE) {
+ if (Util.mean(dB) - Util.mean(noiseDB) < Common.SIGNAL_MIN_STRENGTH_DB_ABOVE_NOISE) {
listener.sendMessage("WARNING: Signal is too weak or background noise is too strong."
+ " Turn up the volume of the playback device or move to a quieter location.\n");
return false;
}
- int indexOf2000Hz = Util.findClosest(AudioCommon.FREQUENCIES_ORIGINAL, 2000.0);
- threshold = dB[indexOf2000Hz] + AudioCommon.PASSING_THRESHOLD_DB;
- int indexOf18500Hz = Util.findClosest(AudioCommon.FREQUENCIES_ORIGINAL, 18500.0);
- int indexOf20000Hz = Util.findClosest(AudioCommon.FREQUENCIES_ORIGINAL, 20000.0);
+ int indexOf2000Hz = Util.findClosest(Common.FREQUENCIES_ORIGINAL, 2000.0);
+ threshold = dB[indexOf2000Hz] + Common.PASSING_THRESHOLD_DB;
+ int indexOf18500Hz = Util.findClosest(Common.FREQUENCIES_ORIGINAL, 18500.0);
+ int indexOf20000Hz = Util.findClosest(Common.FREQUENCIES_ORIGINAL, 20000.0);
double[] responseInRange = new double[indexOf20000Hz - indexOf18500Hz];
System.arraycopy(dB, indexOf18500Hz, responseInRange, 0, responseInRange.length);
if (Util.mean(responseInRange) < threshold) {
@@ -148,15 +128,15 @@
* Calculate the Fourier Coefficient at the pip frequency to calculate the frequency response.
* Package visible for unit testing.
*/
- public double[] measurePipStrength() {
+ double[] measurePipStrength() {
listener.sendMessage("Aligning data... Please wait...\n");
final int dataStartI = alignData();
final int prefixTotalLength = dataStartI
- + Util.toLength(AudioCommon.PREFIX_LENGTH_S + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S, sampleRate);
+ + Util.toLength(Common.PREFIX_LENGTH_S + Common.PAUSE_AFTER_PREFIX_DURATION_S, sampleRate);
listener.sendMessage("Done.\n");
listener.sendMessage("Prefix starts at " + (double) dataStartI / sampleRate + " s \n");
- if (dataStartI > Math.round(sampleRate * (AudioCommon.PREFIX_LENGTH_S
- + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S))) {
+ if (dataStartI > Math.round(sampleRate * (Common.PREFIX_LENGTH_S
+ + Common.PAUSE_BEFORE_PREFIX_DURATION_S + Common.PAUSE_AFTER_PREFIX_DURATION_S))) {
listener.sendMessage("WARNING: Unexpected prefix start time. May have missed the prefix.\n"
+ "PLAY button should be pressed on the playback device within one second"
+ " after RECORD is pressed on the recording device.\n"
@@ -165,17 +145,17 @@
}
listener.sendMessage("Analyzing noise strength... Please wait...\n");
- noisePower = new double[AudioCommon.PIP_NUM][AudioCommon.NOISE_SAMPLES];
- noiseDB = new double[AudioCommon.PIP_NUM];
- for (int s = 0; s < AudioCommon.NOISE_SAMPLES; s++) {
- double[] noisePoints = new double[AudioCommon.WINDOW_FOR_RECORDER.length];
+ noisePower = new double[Common.PIP_NUM][Common.NOISE_SAMPLES];
+ noiseDB = new double[Common.PIP_NUM];
+ for (int s = 0; s < Common.NOISE_SAMPLES; s++) {
+ double[] noisePoints = new double[Common.WINDOW_FOR_RECORDER.length];
System.arraycopy(data, dataStartI - (s + 1) * noisePoints.length - 1,
noisePoints, 0, noisePoints.length);
for (int j = 0; j < noisePoints.length; j++) {
- noisePoints[j] = noisePoints[j] * AudioCommon.WINDOW_FOR_RECORDER[j];
+ noisePoints[j] = noisePoints[j] * Common.WINDOW_FOR_RECORDER[j];
}
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
- double freq = AudioCommon.FREQUENCIES_ORIGINAL[i];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
+ double freq = Common.FREQUENCIES_ORIGINAL[i];
Complex fourierCoeff = new Complex(0, 0);
final Complex rotator = new Complex(0,
-2.0 * Math.PI * freq / sampleRate).exp();
@@ -188,48 +168,48 @@
noisePower[i][s] = fourierCoeff.multiply(fourierCoeff.conjugate()).abs();
}
}
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+ for (int i = 0; i < Common.PIP_NUM; i++) {
double meanNoisePower = 0;
- for (int j = 0; j < AudioCommon.NOISE_SAMPLES; j++) {
+ for (int j = 0; j < Common.NOISE_SAMPLES; j++) {
meanNoisePower += noisePower[i][j];
}
- meanNoisePower /= AudioCommon.NOISE_SAMPLES;
+ meanNoisePower /= Common.NOISE_SAMPLES;
noiseDB[i] = 10 * Math.log10(meanNoisePower);
}
listener.sendMessage("Analyzing pips... Please wait...\n");
- power = new double[AudioCommon.PIP_NUM][AudioCommon.REPETITIONS];
- for (int i = 0; i < AudioCommon.PIP_NUM * AudioCommon.REPETITIONS; i++) {
- if (i % AudioCommon.PIP_NUM == 0) {
- listener.sendMessage("#" + (i / AudioCommon.PIP_NUM + 1) + "\n");
+ power = new double[Common.PIP_NUM][Common.REPETITIONS];
+ for (int i = 0; i < Common.PIP_NUM * Common.REPETITIONS; i++) {
+ if (i % Common.PIP_NUM == 0) {
+ listener.sendMessage("#" + (i / Common.PIP_NUM + 1) + "\n");
}
int pipExpectedStartI;
pipExpectedStartI = prefixTotalLength
- + Util.toLength(i * (AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S), sampleRate);
+ + Util.toLength(i * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S), sampleRate);
// Cut out the data points for the current pip.
- double[] pipPoints = new double[AudioCommon.WINDOW_FOR_RECORDER.length];
+ double[] pipPoints = new double[Common.WINDOW_FOR_RECORDER.length];
System.arraycopy(data, pipExpectedStartI, pipPoints, 0, pipPoints.length);
- for (int j = 0; j < AudioCommon.WINDOW_FOR_RECORDER.length; j++) {
- pipPoints[j] = pipPoints[j] * AudioCommon.WINDOW_FOR_RECORDER[j];
+ for (int j = 0; j < Common.WINDOW_FOR_RECORDER.length; j++) {
+ pipPoints[j] = pipPoints[j] * Common.WINDOW_FOR_RECORDER[j];
}
Complex fourierCoeff = new Complex(0, 0);
final Complex rotator = new Complex(0,
- -2.0 * Math.PI * AudioCommon.FREQUENCIES[i] / sampleRate).exp();
+ -2.0 * Math.PI * Common.FREQUENCIES[i] / sampleRate).exp();
Complex phasor = new Complex(1, 0);
for (int j = 0; j < pipPoints.length; j++) {
fourierCoeff = fourierCoeff.add(phasor.multiply(pipPoints[j]));
phasor = phasor.multiply(rotator);
}
fourierCoeff = fourierCoeff.multiply(1.0 / pipPoints.length);
- int j = AudioCommon.ORDER[i];
- power[j % AudioCommon.PIP_NUM][j / AudioCommon.PIP_NUM] =
+ int j = Common.ORDER[i];
+ power[j % Common.PIP_NUM][j / Common.PIP_NUM] =
fourierCoeff.multiply(fourierCoeff.conjugate()).abs();
}
// Calculate median of trials.
- double[] dB = new double[AudioCommon.PIP_NUM];
- for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+ double[] dB = new double[Common.PIP_NUM];
+ for (int i = 0; i < Common.PIP_NUM; i++) {
dB[i] = 10 * Math.log10(Util.median(power[i]));
}
return dB;
@@ -238,52 +218,41 @@
/**
* Align data using prefix. Package visible for unit testing.
*/
- public int alignData() {
+ int alignData() {
// Zeropadding samples to add in the correlation to avoid FFT wraparound.
- final int zeroPad =
- Util.toLength(AudioCommon.PREFIX_LENGTH_S, AudioCommon.RECORDING_SAMPLE_RATE_HZ) - 1;
- int fftSize = Util.nextPowerOfTwo((int) Math.round(sampleRate * (AudioCommon.PREFIX_LENGTH_S
- + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
- + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S + 0.5))
+ final int zeroPad = Util.toLength(Common.PREFIX_LENGTH_S, Common.RECORDING_SAMPLE_RATE_HZ) - 1;
+ int fftSize = Util.nextPowerOfTwo((int) Math.round(sampleRate * (Common.PREFIX_LENGTH_S
+ + Common.PAUSE_BEFORE_PREFIX_DURATION_S + Common.PAUSE_AFTER_PREFIX_DURATION_S + 0.5))
+ zeroPad);
double[] dataCut = new double[fftSize - zeroPad];
System.arraycopy(data, 0, dataCut, 0, fftSize - zeroPad);
double[] xCorrDataPrefix = Util.computeCrossCorrelation(
Util.padZeros(Util.toComplex(dataCut), fftSize),
- Util.padZeros(Util.toComplex(AudioCommon.PREFIX_FOR_RECORDER), fftSize));
+ Util.padZeros(Util.toComplex(Common.PREFIX_FOR_RECORDER), fftSize));
return Util.findMaxIndex(xCorrDataPrefix);
}
- public double[] getDB() {
+ double[] getDB() {
return dB;
}
- public double[][] getPower() {
+ double[][] getPower() {
return power;
}
- public double[] getNoiseDB() {
+ double[] getNoiseDB() {
return noiseDB;
}
- public double getThreshold() {
+ double getThreshold() {
return threshold;
}
- public boolean getResult() {
+ boolean getResult() {
return result;
}
- public boolean isSilence() {
- for (int i = 0; i < data.length; i++) {
- if (Math.abs(data[i]) > SILENCE_THRESHOLD) {
- return false;
- }
- }
- return true;
- }
-
/**
* An interface for listening a message publishing the progress of the analyzer.
*/
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundGenerator.java
deleted file mode 100644
index 3ea5790..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundGenerator.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.verifier.audio.soundio;
-
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.Util;
-
-/**
- * Sound generator.
- */
-public class SoundGenerator {
-
- private static SoundGenerator instance;
-
- private final byte[] generatedSound;
- private final double[] sample;
-
- private SoundGenerator() {
- // Initialize sample.
- int pipNum = AudioCommon.PIP_NUM;
- int prefixTotalLength =
- Util.toLength(AudioCommon.PREFIX_LENGTH_S, AudioCommon.PLAYING_SAMPLE_RATE_HZ)
- + Util.toLength(AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S,
- AudioCommon.PLAYING_SAMPLE_RATE_HZ)
- + Util.toLength(AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S,
- AudioCommon.PLAYING_SAMPLE_RATE_HZ);
- int repetitionLength = pipNum * Util.toLength(
- AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S,
- AudioCommon.PLAYING_SAMPLE_RATE_HZ);
- int sampleLength = prefixTotalLength + AudioCommon.REPETITIONS * repetitionLength;
- sample = new double[sampleLength];
-
- // Fill sample with prefix.
- System.arraycopy(AudioCommon.PREFIX_FOR_PLAYER, 0, sample,
- Util.toLength(AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S,
- AudioCommon.PLAYING_SAMPLE_RATE_HZ),
- AudioCommon.PREFIX_FOR_PLAYER.length);
-
- // Fill the sample.
- for (int i = 0; i < pipNum * AudioCommon.REPETITIONS; i++) {
- double[] pip = getPip(AudioCommon.WINDOW_FOR_PLAYER, AudioCommon.FREQUENCIES[i]);
- System.arraycopy(pip, 0, sample,
- prefixTotalLength + i * Util.toLength(
- AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S,
- AudioCommon.PLAYING_SAMPLE_RATE_HZ),
- pip.length);
- }
-
- // Convert sample to byte.
- generatedSound = new byte[2 * sample.length];
- int i = 0;
- for (double dVal : sample) {
- short val = (short) ((dVal * 32767));
- generatedSound[i++] = (byte) (val & 0x00ff);
- generatedSound[i++] = (byte) ((val & 0xff00) >>> 8);
- }
- }
-
- public static SoundGenerator getInstance() {
- if (instance == null) {
- instance = new SoundGenerator();
- }
- return instance;
- }
-
- /**
- * Gets a pip sample.
- */
- private static double[] getPip(double[] window, double frequency) {
- int pipArrayLength = window.length;
- double[] pipArray = new double[pipArrayLength];
- double radPerSample = 2 * Math.PI / (AudioCommon.PLAYING_SAMPLE_RATE_HZ / frequency);
- for (int i = 0; i < pipArrayLength; i++) {
- pipArray[i] = window[i] * Math.sin(i * radPerSample);
- }
- return pipArray;
- }
-
- /**
- * Get generated sound in byte[].
- */
- public byte[] getByte() {
- return generatedSound;
- }
-
- /**
- * Get sample in double[].
- */
- public double[] getSample() {
- return sample;
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/RmsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/RmsHelper.java
deleted file mode 100644
index 71d3ec1..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/RmsHelper.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.verifier.audio.wavelib;
-
-public class RmsHelper {
- private double mRmsCurrent;
- public int mBlockSize;
- private int mShoutCount;
- public boolean mRunning = false;
-
- private short[] mAudioShortArray;
-
- private DspBufferDouble mRmsSnapshots;
- private int mShotIndex;
-
- private final float mMinRmsDb;
- private final float mMinRmsVal;
-
- private static final float MAX_VAL = (float)(1 << 15);
-
- public RmsHelper(int blockSize, int shotCount, float minRmsDb, float minRmsVal) {
- mBlockSize = blockSize;
- mShoutCount = shotCount;
- mMinRmsDb = minRmsDb;
- mMinRmsVal = minRmsVal;
-
- reset();
- }
-
- public void reset() {
- mAudioShortArray = new short[mBlockSize];
- mRmsSnapshots = new DspBufferDouble(mShoutCount);
- mShotIndex = 0;
- mRmsCurrent = 0;
- mRunning = false;
- }
-
- public void captureShot() {
- if (mShotIndex >= 0 && mShotIndex < mRmsSnapshots.getSize()) {
- mRmsSnapshots.setValue(mShotIndex++, mRmsCurrent);
- }
- }
-
- public void setRunning(boolean running) {
- mRunning = running;
- }
-
- public double getRmsCurrent() {
- return mRmsCurrent;
- }
-
- public DspBufferDouble getRmsSnapshots() {
- return mRmsSnapshots;
- }
-
- public boolean updateRms(PipeShort pipe, int channelCount, int channel) {
- if (mRunning) {
- int samplesAvailable = pipe.availableToRead();
- while (samplesAvailable >= mBlockSize) {
- pipe.read(mAudioShortArray, 0, mBlockSize);
-
- double rmsTempSum = 0;
- int count = 0;
- for (int i = channel; i < mBlockSize; i += channelCount) {
- float value = mAudioShortArray[i] / MAX_VAL;
-
- rmsTempSum += value * value;
- count++;
- }
- float rms = count > 0 ? (float)Math.sqrt(rmsTempSum / count) : 0f;
- if (rms < mMinRmsVal) {
- rms = mMinRmsVal;
- }
-
- double alpha = 0.9;
- double total_rms = rms * alpha + mRmsCurrent * (1.0f - alpha);
- mRmsCurrent = total_rms;
-
- samplesAvailable = pipe.availableToRead();
- }
- return true;
- }
- return false;
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java
deleted file mode 100644
index 90fa098..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.verifier.camera.its;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.ImageFormat;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.media.Image;
-import android.media.ImageReader;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-import android.util.Size;
-import android.view.Surface;
-import android.view.TextureView;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.ex.camera2.blocking.BlockingCameraManager;
-import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
-import com.android.ex.camera2.blocking.BlockingSessionCallback;
-import com.android.ex.camera2.blocking.BlockingStateCallback;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Test for manual verification of camera privacy hardware switches
- * This test verifies that devices which implement camera hardware
- * privacy toggles enforce sensor privacy when toggles are enabled.
- * - The video stream should be muted:
- * - camera preview & capture should be blank
- * - A dialog or notification should be shown that informs
- * the user that the sensor privacy is enabled.
- */
-public class CameraMuteToggleActivity extends PassFailButtons.Activity
- implements TextureView.SurfaceTextureListener,
- ImageReader.OnImageAvailableListener {
-
- private static final String TAG = "CameraMuteToggleActivity";
- private static final int SESSION_READY_TIMEOUT_MS = 5000;
- private static final int DEFAULT_CAMERA_IDX = 0;
-
- private TextureView mPreviewView;
- private SurfaceTexture mPreviewTexture;
- private Surface mPreviewSurface;
-
- private ImageView mImageView;
-
- private CameraManager mCameraManager;
- private HandlerThread mCameraThread;
- private Handler mCameraHandler;
- private BlockingCameraManager mBlockingCameraManager;
- private CameraCharacteristics mCameraCharacteristics;
- private BlockingStateCallback mCameraListener;
-
- private BlockingSessionCallback mSessionListener;
- private CaptureRequest.Builder mPreviewRequestBuilder;
- private CaptureRequest mPreviewRequest;
- private CaptureRequest.Builder mStillCaptureRequestBuilder;
- private CaptureRequest mStillCaptureRequest;
-
- private CameraCaptureSession mCaptureSession;
- private CameraDevice mCameraDevice;
-
- SizeComparator mSizeComparator = new SizeComparator();
-
- private Size mPreviewSize;
- private Size mJpegSize;
- private ImageReader mJpegImageReader;
-
- private CameraCaptureSession.CaptureCallback mCaptureCallback =
- new CameraCaptureSession.CaptureCallback() {
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.cam_hw_toggle);
-
- setPassFailButtonClickListeners();
-
- mPreviewView = findViewById(R.id.preview_view);
- mImageView = findViewById(R.id.image_view);
-
- mPreviewView.setSurfaceTextureListener(this);
-
- mCameraManager = getSystemService(CameraManager.class);
-
- setInfoResources(R.string.camera_hw_toggle_test, R.string.camera_hw_toggle_test_info, -1);
-
- // Enable Pass button only after taking photo
- setPassButtonEnabled(false);
- setTakePictureButtonEnabled(false);
-
- mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
- mCameraListener = new BlockingStateCallback();
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- startBackgroundThread();
-
- Exception cameraSetupException = null;
- boolean enablePassButton = false;
- try {
- final String[] camerasList = mCameraManager.getCameraIdList();
- if (camerasList.length > 0) {
- String cameraId = mCameraManager.getCameraIdList()[DEFAULT_CAMERA_IDX];
- setUpCamera(cameraId);
- } else {
- showCameraErrorText("");
- }
- } catch (CameraAccessException e) {
- cameraSetupException = e;
- // Enable Pass button for cameras that do not support mute patterns
- // and will disconnect clients if sensor privacy is enabled
- enablePassButton = (e.getReason() == CameraAccessException.CAMERA_DISABLED);
- } catch (BlockingOpenException e) {
- cameraSetupException = e;
- enablePassButton = e.wasDisconnected();
- } finally {
- if (cameraSetupException != null) {
- cameraSetupException.printStackTrace();
- showCameraErrorText(cameraSetupException.getMessage());
- setPassButtonEnabled(enablePassButton);
- }
- }
- }
-
- private void showCameraErrorText(String errorMsg) {
- TextView instructionsText = findViewById(R.id.instruction_text);
- instructionsText.setText(R.string.camera_hw_toggle_test_no_camera);
- instructionsText.append(errorMsg);
- setTakePictureButtonEnabled(false);
- }
-
- @Override
- public void onPause() {
- shutdownCamera();
- stopBackgroundThread();
-
- super.onPause();
- }
-
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
- int width, int height) {
- mPreviewTexture = surfaceTexture;
-
- mPreviewSurface = new Surface(mPreviewTexture);
-
- if (mCameraDevice != null) {
- startPreview();
- }
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
- // Ignored, Camera does all the work for us
- Log.v(TAG, "onSurfaceTextureSizeChanged: " + width + " x " + height);
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- mPreviewTexture = null;
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- // Invoked every time there's a new Camera preview frame
- }
-
- @Override
- public void onImageAvailable(ImageReader reader) {
- Image img = null;
- try {
- img = reader.acquireNextImage();
- if (img == null) {
- Log.d(TAG, "Invalid image!");
- return;
- }
- final int format = img.getFormat();
-
- Bitmap imgBitmap = null;
- if (format == ImageFormat.JPEG) {
- ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
- jpegBuffer.rewind();
- byte[] jpegData = new byte[jpegBuffer.limit()];
- jpegBuffer.get(jpegData);
- imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
- img.close();
- } else {
- Log.i(TAG, "Unsupported image format: " + format);
- }
- if (imgBitmap != null) {
- final Bitmap bitmap = imgBitmap;
- final boolean isMuted = isBitmapMuted(imgBitmap);
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mImageView.setImageBitmap(bitmap);
- // enable pass button if image is muted (black)
- setPassButtonEnabled(isMuted);
- }
- });
- }
- } catch (java.lang.IllegalStateException e) {
- // Swallow exceptions
- e.printStackTrace();
- } finally {
- if (img != null) {
- img.close();
- }
- }
- }
-
- private boolean isBitmapMuted(final Bitmap imgBitmap) {
- // black images may have pixels with values > 0
- // because of JPEG compression artifacts
- final float COLOR_THRESHOLD = 0.02f;
- for (int y = 0; y < imgBitmap.getHeight(); y++) {
- for (int x = 0; x < imgBitmap.getWidth(); x++) {
- Color pixelColor = Color.valueOf(imgBitmap.getPixel(x, y));
- if (pixelColor.red() > COLOR_THRESHOLD || pixelColor.green() > COLOR_THRESHOLD
- || pixelColor.blue() > COLOR_THRESHOLD) {
- return false;
- }
- }
- }
- return true;
- }
-
- private class SizeComparator implements Comparator<Size> {
- @Override
- public int compare(Size lhs, Size rhs) {
- long lha = lhs.getWidth() * lhs.getHeight();
- long rha = rhs.getWidth() * rhs.getHeight();
- if (lha == rha) {
- lha = lhs.getWidth();
- rha = rhs.getWidth();
- }
- return (lha < rha) ? -1 : (lha > rha ? 1 : 0);
- }
- }
-
- private void setUpCamera(String cameraId) throws CameraAccessException, BlockingOpenException {
- shutdownCamera();
-
- mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
- mCameraDevice = mBlockingCameraManager.openCamera(cameraId,
- mCameraListener, mCameraHandler);
-
- StreamConfigurationMap config =
- mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
- Arrays.sort(jpegSizes, mSizeComparator);
- // choose smallest image size, image capture is not the point of this test
- mJpegSize = jpegSizes[0];
-
- mJpegImageReader = ImageReader.newInstance(
- mJpegSize.getWidth(), mJpegSize.getHeight(), ImageFormat.JPEG, 1);
- mJpegImageReader.setOnImageAvailableListener(this, mCameraHandler);
-
- if (mPreviewTexture != null) {
- startPreview();
- }
- }
-
- private void shutdownCamera() {
- if (null != mCaptureSession) {
- mCaptureSession.close();
- mCaptureSession = null;
- }
- if (null != mCameraDevice) {
- mCameraDevice.close();
- mCameraDevice = null;
- }
- if (null != mJpegImageReader) {
- mJpegImageReader.close();
- mJpegImageReader = null;
- }
- }
-
- /**
- * Starts a background thread and its {@link Handler}.
- */
- private void startBackgroundThread() {
- mCameraThread = new HandlerThread("CameraThreadBackground");
- mCameraThread.start();
- mCameraHandler = new Handler(mCameraThread.getLooper());
- }
-
- /**
- * Stops the background thread and its {@link Handler}.
- */
- private void stopBackgroundThread() {
- mCameraThread.quitSafely();
- try {
- mCameraThread.join();
- mCameraThread = null;
- mCameraHandler = null;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- private Size getPreviewSize(int minWidth) {
- StreamConfigurationMap config =
- mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- Size[] outputSizes = config.getOutputSizes(SurfaceTexture.class);
- Arrays.sort(outputSizes, mSizeComparator);
- // choose smallest image size that's at least minWidth
- // image capture is not the point of this test
- for (Size outputSize : outputSizes) {
- if (outputSize.getWidth() > minWidth) {
- return outputSize;
- }
- }
- return outputSizes[0];
- }
-
- private void startPreview() {
- try {
- mPreviewSize = getPreviewSize(256);
-
- mPreviewTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
- mPreviewRequestBuilder =
- mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- mPreviewRequestBuilder.addTarget(mPreviewSurface);
-
- mStillCaptureRequestBuilder =
- mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
- mStillCaptureRequestBuilder.addTarget(mPreviewSurface);
- mStillCaptureRequestBuilder.addTarget(mJpegImageReader.getSurface());
-
- mSessionListener = new BlockingSessionCallback();
- List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/3);
- outputSurfaces.add(mPreviewSurface);
- outputSurfaces.add(mJpegImageReader.getSurface());
- mCameraDevice.createCaptureSession(outputSurfaces, mSessionListener, mCameraHandler);
- mCaptureSession = mSessionListener.waitAndGetSession(/*timeoutMs*/3000);
-
- mPreviewRequest = mPreviewRequestBuilder.build();
- mStillCaptureRequest = mStillCaptureRequestBuilder.build();
-
- mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mCameraHandler);
-
- setTakePictureButtonEnabled(true);
- } catch (CameraAccessException e) {
- e.printStackTrace();
- }
- }
-
- private void takePicture() {
- try {
- mCaptureSession.stopRepeating();
- mSessionListener.getStateWaiter().waitForState(
- BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
-
- mCaptureSession.capture(mStillCaptureRequest, mCaptureCallback, mCameraHandler);
- } catch (CameraAccessException e) {
- e.printStackTrace();
- }
- }
-
- private void setPassButtonEnabled(boolean enabled) {
- ImageButton pass_button = findViewById(R.id.pass_button);
- pass_button.setEnabled(enabled);
- }
-
- private void setTakePictureButtonEnabled(boolean enabled) {
- Button takePhoto = findViewById(R.id.take_picture_button);
- takePhoto.setOnClickListener(v -> takePicture());
- takePhoto.setEnabled(enabled);
- }
-
- @Override
- public void recordTestResults() {
- CtsVerifierReportLog reportLog = getReportLog();
- reportLog.submit();
- }
-}
diff --git a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
index f107562..8682ef6 100644
--- a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
@@ -34,6 +34,16 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
</intent-filter>
</receiver>
+
+ <!-- This activity is used to show "Work Policy Info" in Settings for Managed Devices -->
+ <activity android:name=".WorkPolicyInfoActivity"
+ android:exported="true"
+ android:launchMode="singleTask">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <action android:name="android.settings.SHOW_WORK_POLICY_INFO"/>
+ </intent-filter>
+ </activity>
</application>
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
</manifest>
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java
index b2d496c..a022936 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java
@@ -31,36 +31,46 @@
private static final int FIRST_API_LEVEL =
SystemProperties.getInt("ro.product.first_api_level", 0);
+ private static void verifyVersion(int version) {
+ if (version == Build.VERSION_CODES.CUR_DEVELOPMENT) {
+ throw new RuntimeException("Invalid version: " + version);
+ }
+ }
+
public static boolean isBefore(int version) {
+ verifyVersion(version);
return Build.VERSION.SDK_INT < version;
}
public static boolean isBefore(String version) {
- return Build.VERSION.SDK_INT < resolveVersionString(version);
+ return isBefore(resolveVersionString(version));
}
public static boolean isAfter(int version) {
+ verifyVersion(version);
return Build.VERSION.SDK_INT > version;
}
public static boolean isAfter(String version) {
- return Build.VERSION.SDK_INT > resolveVersionString(version);
+ return isAfter(resolveVersionString(version));
}
public static boolean isAtLeast(int version) {
+ verifyVersion(version);
return Build.VERSION.SDK_INT >= version;
}
public static boolean isAtLeast(String version) {
- return Build.VERSION.SDK_INT >= resolveVersionString(version);
+ return isAtLeast(resolveVersionString(version));
}
public static boolean isAtMost(int version) {
+ verifyVersion(version);
return Build.VERSION.SDK_INT <= version;
}
public static boolean isAtMost(String version) {
- return Build.VERSION.SDK_INT <= resolveVersionString(version);
+ return isAtMost(resolveVersionString(version));
}
public static int getApiLevel() {
@@ -68,19 +78,21 @@
}
public static boolean isFirstApiBefore(int version) {
+ verifyVersion(version);
return FIRST_API_LEVEL < version;
}
public static boolean isFirstApiBefore(String version) {
- return FIRST_API_LEVEL < resolveVersionString(version);
+ return isFirstApiBefore(resolveVersionString(version));
}
public static boolean isFirstApiAfter(int version) {
+ verifyVersion(version);
return FIRST_API_LEVEL > version;
}
public static boolean isFirstApiAfter(String version) {
- return FIRST_API_LEVEL > resolveVersionString(version);
+ return isFirstApiAfter(resolveVersionString(version));
}
public static boolean isFirstApiAtLeast(int version) {
@@ -88,7 +100,7 @@
}
public static boolean isFirstApiAtLeast(String version) {
- return FIRST_API_LEVEL >= resolveVersionString(version);
+ return isFirstApiAtLeast(resolveVersionString(version));
}
public static boolean isFirstApiAtMost(int version) {
@@ -96,7 +108,7 @@
}
public static boolean isFirstApiAtMost(String version) {
- return FIRST_API_LEVEL <= resolveVersionString(version);
+ return isFirstApiAtMost(resolveVersionString(version));
}
public static int getFirstApiLevel() {
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index 1993f30..a176499 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -30,12 +30,14 @@
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.FileUtil;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableCollection;
@@ -52,10 +54,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -79,6 +84,7 @@
private static ImmutableList<String> sSharedLibJars;
private static ImmutableList<SharedLibraryInfo> sSharedLibs;
private static ImmutableMultimap<String, String> sJarsToClasses;
+ private static ImmutableMultimap<String, String> sJarsToFiles;
private DeviceSdkLevel mDeviceSdkLevel;
@@ -780,6 +786,8 @@
.filter(file -> !file.contains("com.google.android.gms"))
.collect(ImmutableList.toImmutableList());
+ final ImmutableSetMultimap.Builder<String, String> jarsToFiles =
+ ImmutableSetMultimap.builder();
final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
ImmutableSetMultimap.builder();
Stream.of(sBootclasspathJars.stream(),
@@ -787,21 +795,32 @@
sSharedLibJars.stream())
.reduce(Stream::concat).orElseGet(Stream::empty)
.parallel()
- .forEach(jar -> {
+ .forEach(jarPath -> {
+ File jar = null;
try {
+ jar = pullJarFromDevice(testInfo.getDevice(), jarPath);
+
+ ImmutableSet<String> files = getJarFileContents(jar);
+ synchronized (jarsToFiles) {
+ jarsToFiles.putAll(jarPath, files);
+ }
+
ImmutableSet<String> classes =
- Classpaths.getClassDefsFromJar(testInfo.getDevice(), jar).stream()
+ Classpaths.getClassDefsFromJar(jar).stream()
.map(ClassDef::getType)
// Inner classes always go with their parent.
.filter(className -> !className.contains("$"))
.collect(ImmutableSet.toImmutableSet());
synchronized (jarsToClasses) {
- jarsToClasses.putAll(jar, classes);
+ jarsToClasses.putAll(jarPath, classes);
}
} catch (DeviceNotAvailableException | IOException e) {
throw new RuntimeException(e);
+ } finally {
+ FileUtil.deleteFile(jar);
}
});
+ sJarsToFiles = jarsToFiles.build();
sJarsToClasses = jarsToClasses.build();
}
@@ -965,9 +984,11 @@
Arrays.stream(collectApkInApexPaths())
.parallel()
.forEach(apk -> {
+ File apkFile = null;
try {
+ apkFile = pullJarFromDevice(getDevice(), apk);
final ImmutableSet<String> apkClasses =
- Classpaths.getClassDefsFromJar(getDevice(), apk).stream()
+ Classpaths.getClassDefsFromJar(apkFile).stream()
.map(ClassDef::getType)
.collect(ImmutableSet.toImmutableSet());
// b/226559955: The directory paths containing APKs contain the build ID,
@@ -991,6 +1012,8 @@
}
} catch (Exception e) {
throw new RuntimeException(e);
+ } finally {
+ FileUtil.deleteFile(apkFile);
}
});
assertThat(perApkClasspathDuplicates).isEmpty();
@@ -1027,6 +1050,46 @@
).isEmpty();
}
+ /**
+ * Ensure that there are no kotlin files in BOOTCLASSPATH, SYSTEMSERVERCLASSPATH
+ * and shared library jars.
+ */
+ @Test
+ public void testNoKotlinFilesInClasspaths() {
+ ImmutableList<String> kotlinFiles =
+ Stream.of(sBootclasspathJars.stream(),
+ sSystemserverclasspathJars.stream(),
+ sSharedLibJars.stream())
+ .reduce(Stream::concat).orElseGet(Stream::empty)
+ .parallel()
+ .filter(jarPath -> {
+ return sJarsToFiles
+ .get(jarPath)
+ .stream()
+ .anyMatch(file -> file.contains(".kotlin_builtins")
+ || file.contains(".kotlin_module"));
+ })
+ .collect(ImmutableList.toImmutableList());
+ assertThat(kotlinFiles).isEmpty();
+ }
+
+ private static File pullJarFromDevice(INativeDevice device,
+ String remoteJarPath) throws DeviceNotAvailableException {
+ File jar = device.pullFile(remoteJarPath);
+ if (jar == null) {
+ throw new IllegalStateException("could not pull remote file " + remoteJarPath);
+ }
+ return jar;
+ }
+
+ private static ImmutableSet<String> getJarFileContents(File jar) throws IOException {
+ try (JarFile jarFile = new JarFile(jar)) {
+ return jarFile.stream()
+ .map(JarEntry::getName)
+ .collect(ImmutableSet.toImmutableSet());
+ }
+ }
+
private boolean isLegacyAndroidxDependency(
ImmutableMap<String, ImmutableSet<String>> legacyExemptAndroidxSharedLibsJarToClasses,
String jar, String className) {
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index d027e21..37c15ac 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -14,6 +14,17 @@
# Bug component: 36137 = per-file SharedUserIdTest.java
# Bug component: 36137 = per-file SplitTests.java
+# Storage bug component
+# Bug component: 46626 = per-file *Adoptable*
+# Bug component: 46626 = per-file *DirectBoot*
+# Bug component: 46626 = per-file *Storage*
+# Bug component: 46626 = per-file *Documents*
+# Bug component: 46626 = per-file ScopedDirectoryAccessTest.java
+
+# DocsUI bug component
+# Bug component: 46626 = per-file *Documents*
+# Bug component: 46626 = per-file ScopedDirectoryAccessTest.java
+
patb@google.com
per-file AccessSerialNumberTest.java = ewol@google.com
per-file ApexSignatureVerificationTest.java = dariofreni@google.com
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 6a65c5b..099a0ab 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -1131,6 +1131,11 @@
}
@Test
+ // There is a minor bug which, alghough fixed in sc-dev (aosp/1834457),
+ // cannot be propagated to the already released sc-release branche
+ // (b/234145920), where mainline-modules are tested.
+ // Skip this test in S to avoid failures in outdated targets.
+ @SdkSuppress(minSdkVersion = 33, codeName = "T")
public void testAppendUpdatesMtime() throws Exception {
writeAndCheckMtime(true);
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
index f07f9d3..c7feda6 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
@@ -61,13 +61,12 @@
protected void setUp() throws Exception {
super.setUp();
- // Temporarily commented out until the Trusted Hotword requirement is enforced again.
- // mTransformedFromOp.clear();
- // // The hotword op is allowed to all UIDs on TV and Auto devices.
- // if (!(DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)
- // || DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY))) {
- // mTransformedFromOp.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD);
- // }
+ mTransformedFromOp.clear();
+ // The hotword op is allowed to all UIDs on TV and Auto devices.
+ if (!(DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)
+ || DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY))) {
+ mTransformedFromOp.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD);
+ }
assertThat(mCtsBuild).isNotNull();
ConfigUtils.removeConfig(getDevice());
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
index cc8022d3..28051e2 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -37,6 +37,9 @@
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
import android.net.Uri;
import android.provider.MediaStore;
@@ -384,13 +387,22 @@
// Test 2: Click Mute Button
// Click to unmute the audio
clickAndWait(muteButton);
+
+ waitForBinderCallsToComplete();
+
// Check that mute button state is unmute, i.e., it shows `volume up` icon
assertMuteButtonState(muteButton, /* isMuted */ false);
// Click on the muteButton and check that mute button status is now 'mute'
clickAndWait(muteButton);
+
+ waitForBinderCallsToComplete();
+
assertMuteButtonState(muteButton, /* isMuted */ true);
// Click on the muteButton and check that mute button status is now unmute
clickAndWait(muteButton);
+
+ waitForBinderCallsToComplete();
+
assertMuteButtonState(muteButton, /* isMuted */ false);
// Test 3: Next preview resumes mute state
@@ -398,6 +410,8 @@
mDevice.pressBack();
clickAndWait(findViewSelectedButton());
+ waitForBinderCallsToComplete();
+
// check that player controls are visible
assertPlayerControlsVisible(playPauseButton, muteButton);
assertMuteButtonState(muteButton, /* isMuted */ false);
@@ -421,6 +435,9 @@
assertMuteButtonState(muteButton, /* isMuted */ true);
// Swipe to next page and check that muteButton is in mute state.
swipeLeftAndWait();
+
+ waitForBinderCallsToComplete();
+
// set-up and wait for player controls to be sticky
setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton);
assertMuteButtonState(muteButton, /* isMuted */ true);
@@ -428,9 +445,15 @@
// Test 2: Swipe resumes mute state, with state of mute button 'volume up' / 'unmute'
// Click muteButton again to check the next video resumes the previous video's mute state
clickAndWait(muteButton);
+
+ waitForBinderCallsToComplete();
+
assertMuteButtonState(muteButton, /* isMuted */ false);
// check that next video resumed previous video's mute state
swipeLeftAndWait();
+
+ waitForBinderCallsToComplete();
+
// Wait for 1s before checking Play/Pause button's visibility
playPauseButton.waitForExists(1000);
// check that player controls are visible
@@ -442,6 +465,78 @@
}
@Test
+ public void testVideoPreviewAudioFocus() throws Exception {
+ final int[] focusStateForTest = new int[1];
+ final AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+ AudioFocusRequest audioFocusRequest =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.USAGE_MEDIA)
+ .setUsage(AudioAttributes.CONTENT_TYPE_MOVIE)
+ .build())
+ .setWillPauseWhenDucked(true)
+ .setAcceptsDelayedFocusGain(false)
+ .setOnAudioFocusChangeListener(focusChange -> {
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+ || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
+ || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ focusStateForTest[0] = focusChange;
+ }
+ })
+ .build();
+
+ // Request AudioFocus
+ assertWithMessage("Expected requestAudioFocus result")
+ .that(audioManager.requestAudioFocus(audioFocusRequest))
+ .isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ // Launch Preview
+ launchPreviewMultipleWithVideos(/* videoCount */ 2);
+ // Video preview launches in mute mode, hence, test's audio focus shouldn't be lost when
+ // video preview starts
+ assertThat(focusStateForTest[0]).isEqualTo(0);
+
+ final UiObject muteButton = findMuteButton();
+ // unmute the audio of video preview
+ clickAndWait(muteButton);
+
+ // Remote video preview involves binder calls
+ // Wait for Binder calls to complete and device to be idle
+ MediaStore.waitForIdle(mContext.getContentResolver());
+ mDevice.waitForIdle();
+
+ assertMuteButtonState(muteButton, /* isMuted */ false);
+
+ // Verify that test lost the audio focus because PhotoPicker has requested audio focus now.
+ assertThat(focusStateForTest[0]).isEqualTo(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+
+ // Reset the focusStateForTest to verify test loses audio focus when video preview is
+ // launched with unmute state
+ focusStateForTest[0] = 0;
+ // Abandon the audio focus before requesting again. This is necessary to reduce test flakes
+ audioManager.abandonAudioFocusRequest(audioFocusRequest);
+ // Request AudioFocus from test again
+ assertWithMessage("Expected requestAudioFocus result")
+ .that(audioManager.requestAudioFocus(audioFocusRequest))
+ .isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ // Wait for PhotoPicker to lose Audio Focus
+ findPlayButton().waitForExists(SHORT_TIMEOUT);
+ // Test requesting audio focus will make PhotoPicker lose audio focus, Verify video is
+ // paused when PhotoPicker loses audio focus.
+ assertWithMessage("PlayPause button's content description")
+ .that(findPlayPauseButton().getContentDescription())
+ .isEqualTo("Play");
+
+ // Swipe to next video and verify preview gains audio focus
+ swipeLeftAndWait();
+ findPauseButton().waitForExists(SHORT_TIMEOUT);
+ // Video preview is now in unmute mode. Hence, PhotoPicker will request audio focus. Verify
+ // that test lost the audio focus.
+ assertThat(focusStateForTest[0]).isEqualTo(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+ }
+
+ @Test
@Ignore("Re-enable once we find work around for b/226318844")
public void testMultiSelect_previewVideoControlsVisibility() throws Exception {
launchPreviewMultipleWithVideos(/* videoCount */ 3);
@@ -576,6 +671,10 @@
final long playbackStartTimeout = 10000;
(findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout);
+ waitForBinderCallsToComplete();
+ }
+
+ private void waitForBinderCallsToComplete() {
// Wait for Binder calls to complete and device to be idle
MediaStore.waitForIdle(mContext.getContentResolver());
mDevice.waitForIdle();
@@ -650,6 +749,14 @@
REGEX_PACKAGE_NAME + ":id/exo_play_pause"));
}
+ private static UiObject findPauseButton() {
+ return new UiObject(new UiSelector().descriptionContains("Pause"));
+ }
+
+ private static UiObject findPlayButton() {
+ return new UiObject(new UiSelector().descriptionContains("Play"));
+ }
+
private static UiObject findPreviewVideoImageView() {
return new UiObject(new UiSelector().resourceIdMatches(
REGEX_PACKAGE_NAME + ":id/preview_video_image"));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
index 51033bb..bb83db1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -64,7 +64,6 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.WindowUtil;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -84,13 +83,11 @@
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- @Ignore("b/168446060")
@Test
public void testShowAndHide_renderSynchronouslyBetweenImeWindowAndAppContent() throws Throwable {
runTest(false /* useControlApi */);
}
- @Ignore("b/168446060")
@Test
public void testControl_rendersSynchronouslyBetweenImeWindowAndAppContent() throws Throwable {
runTest(true /* useControlApi */);
diff --git a/tests/ondevicepersonalization/TEST_MAPPING b/tests/ondevicepersonalization/TEST_MAPPING
new file mode 100644
index 0000000..8d96b25
--- /dev/null
+++ b/tests/ondevicepersonalization/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsOnDevicePersonalizationTestCases"
+ }
+ ]
+}
diff --git a/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java b/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java
index 16db9a9..234a0cd 100644
--- a/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java
+++ b/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import android.content.Context;
-import android.ondevicepersonalization.OnDevicePersonalizationManager;
+import android.ondevicepersonalization.OnDevicePersonalizationManaging;
import androidx.test.core.app.ApplicationProvider;
@@ -29,17 +29,17 @@
import org.junit.runners.JUnit4;
/**
- * Test of {@link OnDevicePersonalizationManager}
+ * Test of {@link OnDevicePersonalizationManaging}
*/
@RunWith(JUnit4.class)
public class OnDevicePersonalizationServiceTest {
private Context mContext;
- private OnDevicePersonalizationManager mService;
+ private OnDevicePersonalizationManaging mService;
@Before
public void setup() throws Exception {
mContext = ApplicationProvider.getApplicationContext();
- mService = new OnDevicePersonalizationManager(mContext);
+ mService = mContext.getSystemService(OnDevicePersonalizationManaging.class);
}
@Test
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
index cdbd58c..4b91ee0 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
@@ -132,6 +132,14 @@
}
}
+fun runPermissionEventCleanupJob(context: Context) {
+ eventually {
+ runShellCommandOrThrow("cmd jobscheduler run -u " +
+ "${Process.myUserHandle().identifier} -f " +
+ "${context.packageManager.permissionControllerPackageName} 3")
+ }
+}
+
inline fun withApp(
apk: String,
packageName: String,
@@ -211,6 +219,7 @@
fun goHome() {
runShellCommandOrThrow("input keyevent KEYCODE_HOME")
+ waitForIdle()
}
/**
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 7de2727..b724be8 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -29,6 +29,7 @@
import android.net.Uri
import android.os.Build
import android.platform.test.annotations.AppModeFull
+import android.provider.DeviceConfig
import android.support.test.uiautomator.By
import android.support.test.uiautomator.BySelector
import android.support.test.uiautomator.UiObject2
@@ -38,6 +39,7 @@
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SdkSuppress
import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
import com.android.compatibility.common.util.MatcherUtils.hasTextThat
@@ -82,7 +84,6 @@
*/
@RunWith(AndroidJUnit4::class)
class AutoRevokeTest {
-
private val context: Context = InstrumentationRegistry.getTargetContext()
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -94,8 +95,14 @@
private lateinit var preMinVersionApkPath: String
private lateinit var preMinVersionAppPackageName: String
+ @Rule
+ @JvmField
+ val storeExactTimeRule = DeviceConfigStateChangerRule(context,
+ DeviceConfig.NAMESPACE_PERMISSIONS, STORE_EXACT_TIME_KEY, "true")
+
companion object {
const val LOG_TAG = "AutoRevokeTest"
+ private const val STORE_EXACT_TIME_KEY = "permission_changes_store_exact_time"
@JvmStatic
@BeforeClass
@@ -253,6 +260,61 @@
@AppModeFull(reason = "Uses separate apps for testing")
@Test
+ fun testAppWithPermissionsChangedRecently_doesNotGetPermissionRevoked() {
+ val unusedThreshold = 15_000L
+ withUnusedThresholdMs(unusedThreshold) {
+ withDummyApp {
+ // Setup
+ // Ensure app is considered unused and then change permission
+ Thread.sleep(unusedThreshold)
+ goToPermissions()
+ click("Calendar")
+ click("Allow")
+ goBack()
+ goBack()
+ goBack()
+
+ // Run
+ runAppHibernationJob(context, LOG_TAG)
+
+ // Verify that permission is not revoked because the permission was changed
+ // within the unused threshold even though the app itself is unused
+ assertPermission(PERMISSION_GRANTED)
+ }
+ }
+ }
+
+ @AppModeFull(reason = "Uses separate apps for testing")
+ @Test
+ fun testPermissionEventCleanupService_scrubsEvents() {
+ val unusedThreshold = 15_000L
+ withUnusedThresholdMs(unusedThreshold) {
+ withDummyApp {
+ // Setup
+ // Ensure app is considered unused
+ Thread.sleep(unusedThreshold)
+ goToPermissions()
+ click("Calendar")
+ click("Allow")
+ goBack()
+ goBack()
+ goBack()
+ // Run with threshold where events would be cleaned up
+ withUnusedThresholdMs(0) {
+ runPermissionEventCleanupJob(context)
+ Thread.sleep(3000L)
+ }
+
+ runAppHibernationJob(context, LOG_TAG)
+
+ // Verify that permission is revoked because there are no recent permission changes
+ assertPermission(PERMISSION_DENIED)
+ }
+ }
+ }
+
+ @AppModeFull(reason = "Uses separate apps for testing")
+ @Test
fun testPreMinAutoRevokeVersionUnusedApp_doesntGetPermissionRevoked() {
withUnusedThresholdMs(3L) {
withDummyApp(preMinVersionApkPath, preMinVersionAppPackageName) {
@@ -436,6 +498,7 @@
waitFindObject(By.res("com.android.permissioncontroller:id/permission_allow_button"))
.click()
}
+ waitForIdle()
}
private fun clickUninstallIcon() {
diff --git a/tests/tests/permission/Android.bp b/tests/tests/permission/Android.bp
index eae7276..5c861b5 100644
--- a/tests/tests/permission/Android.bp
+++ b/tests/tests/permission/Android.bp
@@ -47,6 +47,8 @@
// which provides assertThrows
"testng",
"bluetooth-test-util-lib",
+ "CtsAccessibilityCommon",
+ "safety-center-internal-data",
],
jni_libs: [
"libctspermission_jni",
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index dc19893..8f3d39b 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -71,6 +71,15 @@
<action android:name="android.service.notification.NotificationListenerService"/>
</intent-filter>
</service>
+ <service android:name=".AccessibilityTestService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/test_accessibilityservice"/>
+ </service>
</application>
<!--
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 58ee52f..56a72c9 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -91,6 +91,7 @@
<option name="push" value="CtsStorageEscalationApp28.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp28.apk" />
<option name="push" value="CtsStorageEscalationApp29Full.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Full.apk" />
<option name="push" value="CtsStorageEscalationApp29Scoped.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Scoped.apk" />
+ <option name="push" value="CtsAppThatHasNotificationListener.apk->/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk" />
</target_preparer>
<!-- Remove additional apps if installed -->
@@ -102,6 +103,7 @@
<option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.userapp" />
<option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp" />
<option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.appthathasnotificationlistener" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission/AppThatHasNotificationListener/Android.bp b/tests/tests/permission/AppThatHasNotificationListener/Android.bp
new file mode 100644
index 0000000..419ab5d
--- /dev/null
+++ b/tests/tests/permission/AppThatHasNotificationListener/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatHasNotificationListener",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ sdk_version: "current",
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+}
diff --git a/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml b/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml
new file mode 100644
index 0000000..03d23df
--- /dev/null
+++ b/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthathasnotificationlistener"
+ android:versionCode="1">
+
+ <application android:label="CtsNotificationListener">
+ <service
+ android:name=".CtsNotificationListenerService"
+ android:label="CtsNotificationListener"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java b/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
new file mode 100644
index 0000000..2bd423e
--- /dev/null
+++ b/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts.appthathasnotificationlistener;
+
+import android.service.notification.NotificationListenerService;
+
+public class CtsNotificationListenerService extends NotificationListenerService {}
diff --git a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
index dfcc38e..8dd3e96 100644
--- a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
+++ b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
@@ -16,9 +16,16 @@
package android.permission.cts;
+import android.app.UiAutomation;
+import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assert;
/** Common test utilities */
public class TestUtils {
@@ -123,4 +130,42 @@
}
}
}
+
+ /**
+ * Run the job and then wait for completion
+ */
+ public static void runJobAndWaitUntilCompleted(
+ String packageName,
+ int jobId, long timeout) {
+ runJobAndWaitUntilCompleted(packageName, jobId, timeout,
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+ }
+
+ /**
+ * Run the job and then wait for completion
+ */
+ public static void runJobAndWaitUntilCompleted(
+ String packageName,
+ int jobId,
+ long timeout,
+ UiAutomation automation) {
+ String runJobCmd = "cmd jobscheduler run -u " + Process.myUserHandle().getIdentifier()
+ + " -f " + packageName + " " + jobId;
+ String statusCmd = "cmd jobscheduler get-job-state -u "
+ + Process.myUserHandle().getIdentifier() + " " + packageName + " " + jobId;
+
+ SystemUtil.runWithShellPermissionIdentity(automation, () -> {
+ SystemUtil.runShellCommand(automation, runJobCmd);
+ Thread.sleep(500);
+ try {
+ eventually(() -> Assert.assertEquals(
+ "The job is probably still running",
+ "waiting",
+ SystemUtil.runShellCommand(automation, statusCmd).trim()),
+ timeout);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
}
diff --git a/tests/tests/permission/res/xml/test_accessibilityservice.xml b/tests/tests/permission/res/xml/test_accessibilityservice.xml
new file mode 100644
index 0000000..fa87e2e
--- /dev/null
+++ b/tests/tests/permission/res/xml/test_accessibilityservice.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:canRetrieveWindowContent="true"
+ android:accessibilityFlags="flagDefault"
+ android:notificationTimeout="0" />
diff --git a/tests/tests/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt b/tests/tests/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
new file mode 100644
index 0000000..58238e3
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Process
+import android.permission.cts.NotificationListenerUtils.assertEmptyNotification
+import android.permission.cts.NotificationListenerUtils.assertNotificationExist
+import android.permission.cts.NotificationListenerUtils.cancelNotification
+import android.permission.cts.NotificationListenerUtils.cancelNotifications
+import android.permission.cts.NotificationListenerUtils.getNotification
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueDoesNotExist
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueExist
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterStarted
+import android.permission.cts.SafetyCenterUtils.deviceSupportsSafetyCenter
+import android.permission.cts.SafetyCenterUtils.setDeviceConfigPrivacyProperty
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterManager
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.ProtoUtils
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.server.job.nano.JobSchedulerServiceDumpProto
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+class AccessibilityPrivacySourceTest {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.targetContext
+ private val mAccessibilityServiceRule =
+ InstrumentedAccessibilityServiceTestRule(AccessibilityTestService::class.java, false)
+ private val permissionControllerPackage = context.packageManager.permissionControllerPackageName
+ private val accessibilityTestService =
+ ComponentName(context, AccessibilityTestService::class.java).flattenToString()
+ private val safetyCenterIssueId = "accessibility_$accessibilityTestService"
+ private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)
+
+ @get:Rule
+ val deviceConfigSafetyCenterEnabled =
+ DeviceConfigStateChangerRule(
+ context, DeviceConfig.NAMESPACE_PRIVACY, SAFETY_CENTER_ENABLED, true.toString())
+
+ @get:Rule
+ val deviceConfigA11ySourceEnabled =
+ DeviceConfigStateChangerRule(
+ context, DeviceConfig.NAMESPACE_PRIVACY, ACCESSIBILITY_SOURCE_ENABLED, true.toString())
+
+ @get:Rule
+ val deviceConfigA11yListenerDisabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ ACCESSIBILITY_LISTENER_ENABLED,
+ false.toString())
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(deviceSupportsSafetyCenter(context))
+ InstrumentedAccessibilityService.disableAllServices()
+ runShellCommand("input keyevent KEYCODE_WAKEUP")
+ resetPermissionController()
+ cancelNotifications(permissionControllerPackage)
+ }
+
+ @After
+ fun cleanup() {
+ cancelNotifications(permissionControllerPackage)
+ runWithShellPermissionIdentity { safetyCenterManager?.clearAllSafetySourceDataForTests() }
+ }
+
+ @Test
+ fun testJobSendsNotification() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobSendsIssuesToSafetyCenter() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueExist(
+ SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+ }
+
+ @Test
+ fun testJobDoesNotSendNotificationInSecondRunForSameService() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+ cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testAccessibilityListenerSendsIssueToSafetyCenter() {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
+ val automation = getAutomation()
+ mAccessibilityServiceRule.enableService()
+ TestUtils.eventually(
+ {
+ assertSafetyCenterIssueExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID,
+ automation)
+ },
+ TIMEOUT_MILLIS)
+ automation.destroy()
+ }
+
+ @Test
+ fun testJobWithDisabledServiceDoesNotSendNotification() {
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobWithDisabledServiceDoesNotSendIssueToSafetyCenter() {
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+ }
+
+ @Test
+ fun testJobWithAccessibilityFeatureDisabledDoesNotSendNotification() {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobWithAccessibilityFeatureDisabledDoesNotSendIssueToSafetyCenter() {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+ }
+
+ @Test
+ fun testJobWithSafetyCenterDisabledDoesNotSendNotification() {
+ setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobWithSafetyCenterDisabledDoesNotSendIssueToSafetyCenter() {
+ setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+ }
+
+ @Test
+ fun testNotificationClickOpenSafetyCenter() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ val statusBarNotification =
+ getNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ Assert.assertNotNull(statusBarNotification)
+ val contentIntent = statusBarNotification!!.notification.contentIntent
+ contentIntent.send()
+ assertSafetyCenterStarted()
+ }
+
+ private fun getAutomation(): UiAutomation {
+ return instrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES)
+ }
+
+ private fun runJobAndWaitUntilCompleted() {
+ TestUtils.runJobAndWaitUntilCompleted(
+ permissionControllerPackage, ACCESSIBILITY_JOB_ID, TIMEOUT_MILLIS, getAutomation())
+ }
+
+ /** Reset the permission controllers state. */
+ @Throws(Throwable::class)
+ private fun resetPermissionController() {
+ PermissionUtils.clearAppState(permissionControllerPackage)
+ val currentUserId = Process.myUserHandle().identifier
+
+ // Wait until jobs are cleared
+ TestUtils.eventually(
+ {
+ val dump = getJobSchedulerDump()
+ for (job in dump!!.registeredJobs) {
+ if (job.dump.sourceUserId == currentUserId &&
+ job.dump.sourcePackageName == permissionControllerPackage) {
+ Assert.assertFalse(
+ job.dump.jobInfo.service.className.contains("AccessibilityJobService"))
+ }
+ }
+ },
+ TIMEOUT_MILLIS)
+
+ runShellCommand(
+ "cmd jobscheduler reset-execution-quota -u " +
+ "${Process.myUserHandle().identifier} $permissionControllerPackage")
+
+ context.sendBroadcast(
+ Intent().apply {
+ setClassName(permissionControllerPackage, AccessibilityOnBootReceiver)
+ setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ setPackage(permissionControllerPackage)
+ })
+
+ // Wait until jobs are set up
+ TestUtils.eventually(
+ {
+ val dump = getJobSchedulerDump()
+ for (job in dump!!.registeredJobs) {
+ if (job.dump.sourceUserId == currentUserId &&
+ job.dump.sourcePackageName == permissionControllerPackage &&
+ job.dump.jobInfo.service.className.contains("AccessibilityJobService")) {
+ return@eventually
+ }
+ }
+ Assert.fail("accessibility job not found")
+ },
+ TIMEOUT_MILLIS)
+ }
+
+ @Throws(Exception::class)
+ private fun getJobSchedulerDump(): JobSchedulerServiceDumpProto? {
+ return ProtoUtils.getProto(
+ getAutomation(),
+ JobSchedulerServiceDumpProto::class.java,
+ ProtoUtils.DUMPSYS_JOB_SCHEDULER)
+ }
+
+ companion object {
+ private const val SC_ACCESSIBILITY_SOURCE_ID = "AndroidAccessibility"
+ private const val ACCESSIBILITY_SOURCE_ENABLED = "sc_accessibility_source_enabled"
+ private const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+ private const val ACCESSIBILITY_LISTENER_ENABLED = "sc_accessibility_listener_enabled"
+
+ private const val ACCESSIBILITY_JOB_ID = 6
+ private const val ACCESSIBILITY_NOTIFICATION_ID = 4
+ private const val TIMEOUT_MILLIS: Long = 10000
+
+ private const val SC_ACCESSIBILITY_ISSUE_TYPE_ID = "accessibility_privacy_issue"
+
+ private const val AccessibilityOnBootReceiver =
+ "com.android.permissioncontroller.privacysources.AccessibilityOnBootReceiver"
+
+ @get:ClassRule
+ @JvmStatic
+ val ctsNotificationListenerHelper =
+ CtsNotificationListenerHelperRule(
+ InstrumentationRegistry.getInstrumentation().targetContext)
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/AccessibilityTestService.kt b/tests/tests/permission/src/android/permission/cts/AccessibilityTestService.kt
new file mode 100644
index 0000000..9f5e3f1
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/AccessibilityTestService.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService
+
+/**
+ * Test Accessibility Service
+ */
+class AccessibilityTestService : InstrumentedAccessibilityService()
diff --git a/tests/tests/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java b/tests/tests/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java
new file mode 100644
index 0000000..6ce52b1
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.os.Process.myUserHandle;
+import static android.permission.cts.PermissionUtils.clearAppState;
+import static android.permission.cts.TestUtils.eventually;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.server.job.nano.JobPackageHistoryProto.START_PERIODIC_JOB;
+import static com.android.server.job.nano.JobPackageHistoryProto.STOP_PERIODIC_JOB;
+
+import static org.junit.Assert.assertNotEquals;
+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 static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import static java.lang.Math.max;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.app.NotificationManager;
+import android.app.UiAutomation;
+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.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.job.nano.JobPackageHistoryProto;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Base test class used for {@code NotificationListenerCheckTest} and
+ * {@code NotificationListenerCheckWithSafetyCenterUnsupportedTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+ + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class BaseNotificationListenerCheckTest {
+ private static final String LOG_TAG = BaseNotificationListenerCheckTest.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ protected static final String TEST_APP_PKG =
+ "android.permission.cts.appthathasnotificationlistener";
+ private static final String TEST_APP_NOTIFICATION_SERVICE =
+ TEST_APP_PKG + ".CtsNotificationListenerService";
+ protected static final String TEST_APP_NOTIFICATION_LISTENER_APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk";
+
+ private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4;
+
+ /**
+ * Device config property for whether notification listener check is enabled on the device
+ */
+ private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED =
+ "notification_listener_check_enabled";
+
+ /**
+ * Device config property for time period in milliseconds after which current enabled
+ * notification
+ * listeners are queried
+ */
+ private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
+ "notification_listener_check_interval_millis";
+
+ private static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
+ SECONDS.toMillis(1);
+
+ private static final String PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW =
+ "qc_max_job_count_per_rate_limiting_window";
+
+ private static final String PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS =
+ "qc_rate_limiting_window_ms";
+
+ /**
+ * ID for notification shown by
+ * {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck}.
+ */
+ public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3;
+
+ protected static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
+ protected static final long ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS = 5000;
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
+ private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation();
+
+ private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
+ .getPermissionControllerPackageName();
+
+ private static List<ComponentName> sPreviouslyEnabledNotificationListeners;
+
+ // Override SafetyCenter enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
+ Boolean.toString(true));
+
+ // Override NlsCheck enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
+ Boolean.toString(true));
+
+ // Override general notification interval from once every day to once ever 1 second
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
+ Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS));
+
+ // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+ @Rule
+ public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig1 =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW,
+ Integer.toString(3000000));
+
+ // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+ @Rule
+ public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig2 =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS,
+ Integer.toString(30000));
+
+ @Rule
+ public CtsNotificationListenerHelperRule ctsNotificationListenerHelper =
+ new CtsNotificationListenerHelperRule(sContext);
+
+ @BeforeClass
+ public static void beforeClassSetup() throws Exception {
+ // Disallow any OEM enabled NLS
+ disallowPreexistingNotificationListeners();
+ }
+
+ @AfterClass
+ public static void afterClassTearDown() throws Throwable {
+ // Reallow any previously OEM allowed NLS
+ reallowPreexistingNotificationListeners();
+ }
+
+ private static void setDeviceConfigPrivacyProperty(String propertyName, String value) {
+ runWithShellPermissionIdentity(() -> {
+ boolean valueWasSet = DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ /* name = */ propertyName,
+ /* value = */ value,
+ /* makeDefault = */ false);
+ if (!valueWasSet) {
+ throw new IllegalStateException("Could not set " + propertyName + " to " + value);
+ }
+ }, WRITE_DEVICE_CONFIG);
+ }
+
+ /**
+ * Enable or disable notification listener check
+ */
+ protected static void setNotificationListenerCheckEnabled(boolean enabled) {
+ setDeviceConfigPrivacyProperty(
+ PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
+ /* value = */ String.valueOf(enabled));
+ }
+
+ /**
+ * Allow or disallow a {@link NotificationListenerService} component for the current user
+ *
+ * @param listenerComponent {@link NotificationListenerService} component to allow or disallow
+ */
+ private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent,
+ boolean allowed) {
+ String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ")
+ + listenerComponent.flattenToString();
+ runShellCommand(command);
+ }
+
+ private static void disallowPreexistingNotificationListeners() {
+ runWithShellPermissionIdentity(() -> {
+ NotificationManager notificationManager =
+ sContext.getSystemService(NotificationManager.class);
+ sPreviouslyEnabledNotificationListeners =
+ notificationManager.getEnabledNotificationListeners();
+ });
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size()
+ + " previously allowed notification listeners. Disabling before test run.");
+ }
+ for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
+ setNotificationListenerServiceAllowed(listener, false);
+ }
+ }
+
+ private static void reallowPreexistingNotificationListeners() {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size()
+ + " previously allowed notification listeners found before test run.");
+ }
+ for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
+ setNotificationListenerServiceAllowed(listener, true);
+ }
+ }
+
+ protected void allowTestAppNotificationListenerService() {
+ setNotificationListenerServiceAllowed(
+ new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true);
+ }
+
+ protected void disallowTestAppNotificationListenerService() {
+ setNotificationListenerServiceAllowed(
+ new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false);
+ }
+
+ /**
+ * Get the state of the job scheduler
+ */
+ private static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception {
+ return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class,
+ ProtoUtils.DUMPSYS_JOB_SCHEDULER);
+ }
+
+ /**
+ * Get the last time the NOTIFICATION_LISTENER_CHECK_JOB_ID job was started/stopped for
+ * permission
+ * controller.
+ *
+ * @param event the job event (start/stop)
+ * @return the last time the event happened.
+ */
+ private static long getLastJobTime(int event) throws Exception {
+ int permControllerUid = sPackageManager.getPackageUid(PERMISSION_CONTROLLER_PKG, 0);
+
+ long lastTime = -1;
+
+ for (JobPackageHistoryProto.HistoryEvent historyEvent :
+ getJobSchedulerDump().history.historyEvent) {
+ if (historyEvent.uid == permControllerUid
+ && historyEvent.jobId == NOTIFICATION_LISTENER_CHECK_JOB_ID
+ && historyEvent.event == event) {
+ lastTime = max(lastTime,
+ System.currentTimeMillis() - historyEvent.timeSinceEventMs);
+ }
+ }
+
+ return lastTime;
+ }
+
+ /**
+ * Force a run of the notification listener check.
+ */
+ protected static void runNotificationListenerCheck() throws Throwable {
+ // Sleep a little to make sure we don't have overlap in timing
+ Thread.sleep(1000);
+
+ long beforeJob = System.currentTimeMillis();
+
+ // Sleep a little to avoid raciness in time keeping
+ Thread.sleep(1000);
+
+ runShellCommand("cmd jobscheduler run -u " + myUserHandle().getIdentifier() + " -f "
+ + PERMISSION_CONTROLLER_PKG + " " + NOTIFICATION_LISTENER_CHECK_JOB_ID);
+
+ eventually(() -> {
+ long startTime = getLastJobTime(START_PERIODIC_JOB);
+ assertTrue(startTime + " !> " + beforeJob, startTime > beforeJob);
+ }, UNEXPECTED_TIMEOUT_MILLIS);
+
+ // We can't simply require startTime <= endTime because the time being reported isn't
+ // accurate, and sometimes the end time may come before the start time by around 100 ms.
+ eventually(() -> {
+ long stopTime = getLastJobTime(STOP_PERIODIC_JOB);
+ assertTrue(stopTime + " !> " + beforeJob, stopTime > beforeJob);
+ }, UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Skip tests for if Safety Center not supported
+ */
+ protected void assumeDeviceSupportsSafetyCenter() {
+ assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+ }
+
+ /**
+ * Skip tests for if Safety Center IS supported
+ */
+ protected void assumeDeviceDoesNotSupportSafetyCenter() {
+ assumeFalse(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+ }
+
+ protected void wakeUpAndDismissKeyguard() {
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ runShellCommand("wm dismiss-keyguard");
+ }
+
+ /**
+ * Reset the permission controllers state before each test
+ */
+ protected void resetPermissionControllerBeforeEachTest() throws Throwable {
+ resetPermissionController();
+
+ // ensure no posted notification listener notifications exits
+ eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Reset job scheduler stats (to allow more jobs to be run)
+ runShellCommand(
+ "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
+ + PERMISSION_CONTROLLER_PKG);
+ }
+
+ /**
+ * Reset the permission controllers state.
+ */
+ private static void resetPermissionController() throws Throwable {
+ clearAppState(PERMISSION_CONTROLLER_PKG);
+ int currentUserId = myUserHandle().getIdentifier();
+
+ // Wait until jobs are cleared
+ eventually(() -> {
+ JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+
+ for (RegisteredJob job : dump.registeredJobs) {
+ if (job.dump.sourceUserId == currentUserId) {
+ assertNotEquals(job.dump.sourcePackageName, PERMISSION_CONTROLLER_PKG);
+ }
+ }
+ }, UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Setup up permission controller again (simulate a reboot)
+ Intent permissionControllerSetupIntent = null;
+ for (ResolveInfo ri : sContext.getPackageManager().queryBroadcastReceivers(
+ new Intent(ACTION_BOOT_COMPLETED), 0)) {
+ String pkg = ri.activityInfo.packageName;
+
+ if (pkg.equals(PERMISSION_CONTROLLER_PKG)) {
+ permissionControllerSetupIntent = new Intent()
+ .setClassName(pkg, ri.activityInfo.name)
+ .setFlags(FLAG_RECEIVER_FOREGROUND)
+ .setPackage(PERMISSION_CONTROLLER_PKG);
+
+ sContext.sendBroadcast(permissionControllerSetupIntent);
+ }
+ }
+
+ // Wait until jobs are set up
+ eventually(() -> {
+ JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+
+ for (RegisteredJob job : dump.registeredJobs) {
+ if (job.dump.sourceUserId == currentUserId
+ && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)
+ && job.dump.jobInfo.service.className.contains(
+ "NotificationListenerCheck")) {
+ return;
+ }
+ }
+
+ fail("Permission controller jobs not found");
+ }, UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Preshow/dismiss cts NotificationListener notification as it negatively affects test results
+ * (can result in unexpected test pass/failures)
+ */
+ protected void triggerAndDismissCtsNotificationListenerNotification() throws Throwable {
+ // CtsNotificationListenerService isn't enabled at this point, but NotificationListener
+ // should be. Mark as notified by showing and dismissing
+ runNotificationListenerCheck();
+
+ // Ensure notification shows and dismiss
+ eventually(() -> assertNotNull(getNotification(true)),
+ UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Get a notification listener notification that is currently visible.
+ *
+ * @param cancelNotification if `true` the notification is canceled inside this method
+ * @return The notification or `null` if there is none
+ */
+ protected StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+ return NotificationUtils.getNotificationForPackageAndId(
+ PERMISSION_CONTROLLER_PKG,
+ NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID,
+ cancelNotification);
+ }
+
+ /**
+ * Clear any notifications related to NotificationListenerCheck to ensure clean test setup
+ */
+ protected void clearNotifications() throws Throwable {
+ // Clear notification if present
+ NotificationUtils.clearNotificationsForPackage(PERMISSION_CONTROLLER_PKG);
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/CtsNotificationListenerHelperRule.kt b/tests/tests/permission/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
new file mode 100644
index 0000000..b7fa960
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.content.ComponentName
+import android.content.Context
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule that enables and disables the CTS NotificationListenerService
+ */
+class CtsNotificationListenerHelperRule(context: Context) : TestRule {
+
+ private val notificationListenerComponentName = ComponentName(
+ context,
+ NotificationListener::class.java
+ )
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ try {
+ // Allow NLS used to verify notifications sent
+ SystemUtil.runShellCommand(ALLOW_NLS_COMMAND +
+ notificationListenerComponentName.flattenToString())
+
+ base.evaluate()
+ } finally {
+ // Disallow NLS used to verify notifications sent
+ SystemUtil.runShellCommand(DISALLOW_NLS_COMMAND +
+ notificationListenerComponentName.flattenToString())
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val ALLOW_NLS_COMMAND = "cmd notification allow_listener "
+ private const val DISALLOW_NLS_COMMAND = "cmd notification disallow_listener "
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index ab7a15d..b41fb08 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -18,9 +18,9 @@
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
-import static android.app.Notification.EXTRA_TITLE;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_NOT_FOREGROUND;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
@@ -50,6 +50,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.PendingIntent;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -62,6 +63,7 @@
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
@@ -78,9 +80,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
import com.android.compatibility.common.util.ProtoUtils;
import com.android.compatibility.common.util.mainline.MainlineModule;
import com.android.compatibility.common.util.mainline.ModuleDetector;
@@ -93,6 +96,7 @@
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -117,10 +121,22 @@
private static final String TEST_APP_LOCATION_FG_ACCESS_APK =
"/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk";
private static final int LOCATION_ACCESS_CHECK_JOB_ID = 0;
+ private static final int LOCATION_ACCESS_CHECK_NOTIFICATION_ID = 0;
- /** Whether to show location access check notifications. */
+ /**
+ * Whether to show location access check notifications.
+ */
private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
"location_access_check_enabled";
+ private static final String PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS =
+ "location_access_check_delay_millis";
+ private static final String PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS =
+ "location_access_check_periodic_interval_millis";
+ private static final String PROPERTY_BG_LOCATION_CHECK_ENABLED = "bg_location_check_is_enabled";
+ private static final String PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW =
+ "qc_max_job_count_per_rate_limiting_window";
+ private static final String PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS =
+ "qc_rate_limiting_window_ms";
private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
private static final long EXPECTED_TIMEOUT_MILLIS = 15000;
@@ -147,16 +163,93 @@
private static ServiceConnection sConnection;
private static IAccessLocationOnCommand sLocationAccessor;
- private DeviceConfigStateHelper mPrivacyDeviceConfig =
- new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_PRIVACY);
- private static DeviceConfigStateHelper sJobSchedulerDeviceConfig =
- new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
-
private static void assumeNotPlayManaged() throws Exception {
assumeFalse(ModuleDetector.moduleIsPlayManaged(
sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
}
+ // Override location access check flag
+ @Rule
+ public DeviceConfigStateChangerRule mPrivacyDeviceConfig =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+ Boolean.toString(true));
+
+ // Override SafetyCenter enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
+ Boolean.toString(true));
+
+ // Override BG location enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgLocationCheckEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_BG_LOCATION_CHECK_ENABLED,
+ Boolean.toString(true));
+
+ // Override general notification interval
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckIntervalMillis =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS,
+ "100");
+
+ // Override general delay interval
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckDelayMillis =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS,
+ "50");
+
+ // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+ @Rule
+ public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig1 =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW,
+ Integer.toString(3000000));
+
+ // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+ @Rule
+ public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig2 =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS,
+ Integer.toString(30000));
+
+ /**
+ * Change settings so that permission controller can show location access notifications more
+ * often.
+ */
+ @BeforeClass
+ public static void reduceDelays() {
+ runWithShellPermissionIdentity(() -> {
+ ContentResolver cr = sContext.getContentResolver();
+ // New settings will be applied in when permission controller is reset
+ Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
+ Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
+ });
+ }
+
+ /**
+ * Reset settings so that permission controller runs normally.
+ */
+ @AfterClass
+ public static void resetDelays() throws Throwable {
+ runWithShellPermissionIdentity(() -> {
+ ContentResolver cr = sContext.getContentResolver();
+ Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
+ Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
+ });
+ }
+
/**
* Connected to {@value #TEST_APP_PKG} and make it access the location in the background
*/
@@ -272,7 +365,6 @@
* controller.
*
* @param event the job event (start/stop)
- *
* @return the last time the event happened.
*/
private static long getLastJobTime(int event) throws Exception {
@@ -358,8 +450,7 @@
return null;
}
- if (notification.getNotification().extras.getString(EXTRA_TITLE, "")
- .contains(TEST_APP_LABEL)) {
+ if (notification.getId() == LOCATION_ACCESS_CHECK_NOTIFICATION_ID) {
if (cancelNotification) {
notificationService.cancelNotification(notification.getKey());
@@ -395,25 +486,6 @@
NotificationListener.class).flattenToString()));
}
- /**
- * Change settings so that permission controller can show location access notifications more
- * often.
- */
- @BeforeClass
- public static void reduceDelays() {
- runWithShellPermissionIdentity(() -> {
- ContentResolver cr = sContext.getContentResolver();
-
- // New settings will be applied in when permission controller is reset
- Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
- Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
-
- // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
- sJobSchedulerDeviceConfig.set("qc_max_job_count_per_rate_limiting_window", "3000000");
- sJobSchedulerDeviceConfig.set("qc_rate_limiting_window_ms", "30000");
- });
- }
-
@BeforeClass
public static void installBackgroundAccessApp() throws Exception {
installBackgroundAccessApp(false);
@@ -444,6 +516,21 @@
sLocationAccessor = null;
}
+ private void setDeviceConfigProperty(
+ @NonNull String propertyName,
+ @NonNull String value) {
+ runWithShellPermissionIdentity(() -> {
+ boolean valueWasSet = DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ propertyName,
+ value,
+ false);
+ if (!valueWasSet) {
+ throw new IllegalStateException("Could not set " + propertyName + " to " + value);
+ }
+ }, WRITE_DEVICE_CONFIG);
+ }
+
private static void installForegroundAccessApp() throws Exception {
unbindService();
@@ -514,8 +601,8 @@
* Enable location access check
*/
public void enableLocationAccessCheck() throws Throwable {
- mPrivacyDeviceConfig.set(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true");
-
+ setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+ "true");
// Run a location access check to update enabled state inside permission controller
runLocationCheck();
}
@@ -524,8 +611,8 @@
* Disable location access check
*/
private void disableLocationAccessCheck() throws Throwable {
- mPrivacyDeviceConfig.set(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "false");
-
+ setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+ "false");
// Run a location access check to update enabled state inside permission controller
runLocationCheck();
}
@@ -631,27 +718,10 @@
}
/**
- * Reset settings so that permission controller runs normally.
- */
- @AfterClass
- public static void resetDelays() throws Throwable {
- runWithShellPermissionIdentity(() -> {
- ContentResolver cr = sContext.getContentResolver();
-
- Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
- Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
-
- sJobSchedulerDeviceConfig.restoreOriginalValues();
- });
- }
-
- /**
* Reset location access check
*/
@After
public void resetPrivacyConfig() throws Throwable {
- mPrivacyDeviceConfig.restoreOriginalValues();
-
// Run a location access check to update enabled state inside permission controller
runLocationCheck();
}
@@ -665,7 +735,6 @@
public void notificationIsShown() throws Throwable {
accessLocation();
runLocationCheck();
-
eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
}
@@ -844,4 +913,23 @@
runLocationCheck();
assertNull(getNotification(false));
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void notificationOnClickOpensSafetyCenter() throws Throwable {
+ accessLocation();
+ runLocationCheck();
+
+ StatusBarNotification currentNotification = eventually(() -> {
+ StatusBarNotification notification = getNotification(false);
+ assertNotNull(notification);
+ return notification;
+ }, EXPECTED_TIMEOUT_MILLIS);
+
+ // Verify content intent
+ PendingIntent contentIntent = currentNotification.getNotification().contentIntent;
+ contentIntent.send();
+
+ SafetyCenterUtils.assertSafetyCenterStarted();
+ }
}
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java
new file mode 100644
index 0000000..3ee161f
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.permission.cts.PermissionUtils.clearAppState;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+import static android.permission.cts.TestUtils.ensure;
+import static android.permission.cts.TestUtils.eventually;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.PendingIntent;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the {@code NotificationListenerCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+ + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class NotificationListenerCheckTest extends BaseNotificationListenerCheckTest {
+
+ @Before
+ public void setup() throws Throwable {
+ // Skip tests if safety center not allowed
+ assumeDeviceSupportsSafetyCenter();
+
+ wakeUpAndDismissKeyguard();
+ resetPermissionControllerBeforeEachTest();
+
+ // Cts NLS is required to verify sent Notifications, however, we don't want it to show up in
+ // testing
+ triggerAndDismissCtsNotificationListenerNotification();
+
+ clearNotifications();
+
+ // Install and allow the app with NLS for testing
+ install(TEST_APP_NOTIFICATION_LISTENER_APK);
+ allowTestAppNotificationListenerService();
+ }
+
+ @After
+ public void tearDown() throws Throwable {
+ // Disallow and uninstall the app with NLS for testing
+ disallowTestAppNotificationListenerService();
+ uninstallApp(TEST_APP_PKG);
+
+ clearNotifications();
+ }
+
+ @Test
+ public void noNotificationIfFeatureDisabled() throws Throwable {
+ setNotificationListenerCheckEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotificationIfSafetyCenterDisabled() throws Throwable {
+ SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShown() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull("Expected notification, none found", getNotification(false)),
+ UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownOnlyOnce() throws Throwable {
+ runNotificationListenerCheck();
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownAgainAfterClear() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ clearAppState(TEST_APP_PKG);
+
+ // Wait until package is cleared and permission controller has cleared the state
+ Thread.sleep(2000);
+
+ allowTestAppNotificationListenerService();
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ uninstallApp(TEST_APP_PKG);
+
+ // Wait until package permission controller has cleared the state
+ Thread.sleep(2000);
+
+ install(TEST_APP_NOTIFICATION_LISTENER_APK);
+
+ allowTestAppNotificationListenerService();
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void removeNotificationOnUninstall() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ uninstallApp(TEST_APP_PKG);
+
+ // Wait until package permission controller has cleared the state
+ Thread.sleep(2000);
+
+ eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsNotShownAfterDisableAppNotificationListener() throws Throwable {
+ disallowTestAppNotificationListenerService();
+
+ runNotificationListenerCheck();
+
+ // We don't expect a notification, but try to trigger one anyway
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationOnClick_opensSafetyCenter() throws Throwable {
+ runNotificationListenerCheck();
+
+ StatusBarNotification currentNotification = eventually(
+ () -> {
+ StatusBarNotification notification = getNotification(false);
+ assertNotNull(notification);
+ return notification;
+ }, UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Verify content intent
+ PendingIntent contentIntent = currentNotification.getNotification().contentIntent;
+ contentIntent.send();
+
+ SafetyCenterUtils.assertSafetyCenterStarted();
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java
new file mode 100644
index 0000000..a346de6
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+import static android.permission.cts.TestUtils.ensure;
+
+import static org.junit.Assert.assertNull;
+
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the {@code NotificationListenerCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+ + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class NotificationListenerCheckWithSafetyCenterUnsupportedTest
+ extends BaseNotificationListenerCheckTest {
+
+ @Before
+ public void setup() throws Throwable {
+ // Skip tests if safety center is supported
+ assumeDeviceDoesNotSupportSafetyCenter();
+
+ wakeUpAndDismissKeyguard();
+ resetPermissionControllerBeforeEachTest();
+
+ clearNotifications();
+
+ // Install and allow the app with NLS for testing
+ install(TEST_APP_NOTIFICATION_LISTENER_APK);
+ allowTestAppNotificationListenerService();
+ }
+
+ @After
+ public void tearDown() throws Throwable {
+ // Disallow and uninstall the app with NLS for testing
+ disallowTestAppNotificationListenerService();
+ uninstallApp(TEST_APP_PKG);
+
+ clearNotifications();
+ }
+
+ @Test
+ public void noNotifications_featureEnabled_safetyCenterEnabled() throws Throwable {
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotifications_featureDisabled_safetyCenterEnabled() throws Throwable {
+ setNotificationListenerCheckEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotifications_featureEnabled_safetyCenterDisabled() throws Throwable {
+ SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotifications_featureDisabled_safetyCenterDisabled() throws Throwable {
+ setNotificationListenerCheckEnabled(false);
+ SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerUtils.kt b/tests/tests/permission/src/android/permission/cts/NotificationListenerUtils.kt
new file mode 100644
index 0000000..0ee8a3d
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListenerUtils.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.service.notification.StatusBarNotification
+import org.junit.Assert
+import android.permission.cts.TestUtils.ensure
+import android.permission.cts.TestUtils.eventually
+
+object NotificationListenerUtils {
+
+ private const val NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS = 5000L
+ private const val NOTIFICATION_WAIT_MILLIS = 2000L
+ private val notificationService = NotificationListener.getInstance()
+
+ @JvmStatic
+ fun assertEmptyNotification(packageName: String, notificationId: Int) {
+ ensure({
+ Assert.assertNull(
+ "Expected no notification",
+ getNotification(packageName, notificationId))
+ }, NOTIFICATION_WAIT_MILLIS)
+ }
+
+ @JvmStatic
+ fun assertNotificationExist(packageName: String, notificationId: Int) {
+ eventually({
+ Assert.assertNotNull(
+ "Expected notification, none found",
+ getNotification(packageName, notificationId))
+ }, NOTIFICATION_WAIT_MILLIS)
+ }
+
+ @JvmStatic
+ fun cancelNotification(packageName: String, notificationId: Int) {
+ val notification = getNotification(packageName, notificationId)
+ if (notification != null) {
+ notificationService.cancelNotification(notification.key)
+ eventually({
+ Assert.assertTrue(getNotification(packageName, notificationId) == null)
+ }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+ }
+ }
+
+ @JvmStatic
+ fun cancelNotifications(packageName: String) {
+ val notifications = getNotifications(packageName)
+ if (notifications.isNotEmpty()) {
+ notifications.forEach { notification ->
+ notificationService.cancelNotification(notification.key)
+ }
+ eventually({
+ Assert.assertTrue(getNotifications(packageName).isEmpty())
+ }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+ }
+ }
+
+ @JvmStatic
+ fun getNotification(packageName: String, notificationId: Int): StatusBarNotification? {
+ return getNotifications(packageName).firstOrNull {
+ it.id == notificationId
+ }
+ }
+
+ @JvmStatic
+ fun getNotifications(packageName: String): List<StatusBarNotification> {
+ val notifications: MutableList<StatusBarNotification> = ArrayList()
+ for (notification in notificationService.activeNotifications) {
+ if (notification.packageName == packageName) {
+ notifications.add(notification)
+ }
+ }
+ return notifications
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationUtils.kt b/tests/tests/permission/src/android/permission/cts/NotificationUtils.kt
new file mode 100644
index 0000000..7c50837
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationUtils.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.service.notification.StatusBarNotification
+import org.junit.Assert
+import java.util.concurrent.TimeUnit
+
+/**
+ * Utility methods to interact with NotificationManager through the CTS NotificationListenerService
+ * to get or clear notifications.
+ */
+object NotificationUtils {
+
+ private val NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5)
+
+ /**
+ * Get a notification listener notification that is currently visible.
+ *
+ * @param cancelNotification if `true` the notification is canceled inside this method
+ * @return The notification or `null` if there is none
+ */
+ @JvmStatic
+ @Throws(Throwable::class)
+ fun getNotificationForPackageAndId(
+ pkg: String,
+ id: Int,
+ cancelNotification: Boolean
+ ): StatusBarNotification? {
+ val notificationService = NotificationListener.getInstance()
+ val notifications: List<StatusBarNotification> = getNotificationsForPackage(pkg)
+ if (notifications.isEmpty()) {
+ return null
+ }
+ for (notification in notifications) {
+ if (notification.id == id) {
+ if (cancelNotification) {
+ clearNotification(notification)
+ }
+ return notification
+ }
+ }
+ return null
+ }
+
+ /**
+ * Clears all currently visible notifications for a specified package.
+ */
+ @JvmStatic
+ @Throws(Throwable::class)
+ fun clearNotificationsForPackage(pkg: String) {
+ val notifications: List<StatusBarNotification> = getNotificationsForPackage(pkg)
+ if (notifications.isEmpty()) {
+ return
+ }
+
+ clearNotifications(notifications)
+ }
+
+ /** Clears the specified notification and ensures (asserts) it was removed */
+ @JvmStatic
+ @Throws(Throwable::class)
+ fun clearNotification(notification: StatusBarNotification) {
+ val notificationService = NotificationListener.getInstance()
+ notificationService.cancelNotification(notification.key)
+
+ // Wait for notification to get canceled
+ TestUtils.eventually({
+ Assert.assertFalse(
+ listOf(*notificationService.activeNotifications)
+ .contains(notification)
+ )
+ }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+ }
+
+ private fun clearNotifications(notifications: List<StatusBarNotification>) {
+ val notificationService = NotificationListener.getInstance()
+ notifications.forEach { notificationService.cancelNotification(it.key) }
+
+ // Wait for notification to get canceled
+ TestUtils.eventually({
+ val activeNotifications: List<StatusBarNotification> =
+ listOf(*notificationService.activeNotifications)
+ Assert.assertFalse(
+ activeNotifications.any { notifications.contains(it) }
+ )
+ }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+ }
+
+ /**
+ * Get all notifications associated with a given package that are currently visible.
+ * @param pkg Package for which to filter the notifications by
+ * @return [List] of [StatusBarNotification]
+ */
+ @Throws(Exception::class)
+ private fun getNotificationsForPackage(pkg: String): List<StatusBarNotification> {
+ val notificationService = NotificationListener.getInstance()
+ val notifications: MutableList<StatusBarNotification> = ArrayList()
+ for (notification in notificationService.activeNotifications) {
+ if (notification.packageName == pkg) {
+ notifications.add(notification)
+ }
+ }
+ return notifications
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/SafetyCenterUtils.kt b/tests/tests/permission/src/android/permission/cts/SafetyCenterUtils.kt
new file mode 100644
index 0000000..f057b21
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SafetyCenterUtils.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.Build
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterManager
+import android.support.test.uiautomator.By
+import androidx.annotation.RequiresApi
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
+import com.android.safetycenter.internaldata.SafetyCenterIds
+import com.android.safetycenter.internaldata.SafetyCenterIssueId
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey
+import org.junit.Assert
+
+object SafetyCenterUtils {
+ /** Name of the flag that determines whether SafetyCenter is enabled. */
+ const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ /** Returns whether the device supports Safety Center. */
+ @JvmStatic
+ fun deviceSupportsSafetyCenter(context: Context): Boolean {
+ return context.resources.getBoolean(
+ Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android"))
+ }
+
+ /** Enabled or disable Safety Center */
+ @JvmStatic
+ fun setSafetyCenterEnabled(enabled: Boolean) {
+ setDeviceConfigPrivacyProperty(PROPERTY_SAFETY_CENTER_ENABLED, enabled.toString())
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @JvmStatic
+ fun startSafetyCenterActivity(context: Context) {
+ context.startActivity(
+ Intent(Intent.ACTION_SAFETY_CENTER)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ }
+
+ @JvmStatic
+ fun assertSafetyCenterStarted() {
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ waitFindObject(By.desc("Security & Privacy"))
+ }
+
+ @JvmStatic
+ fun setDeviceConfigPrivacyProperty(
+ propertyName: String,
+ value: String,
+ uiAutomation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ runWithShellPermissionIdentity(uiAutomation) {
+ val valueWasSet =
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ /* name = */ propertyName,
+ /* value = */ value,
+ /* makeDefault = */ false)
+ check(valueWasSet) { "Could not set $propertyName to $value" }
+ }
+ }
+
+ @JvmStatic
+ private fun getSafetyCenterIssues(
+ automation: UiAutomation = instrumentation.uiAutomation
+ ): List<SafetyCenterIssue> {
+ val safetyCenterManager =
+ instrumentation.targetContext.getSystemService(SafetyCenterManager::class.java)
+ val issues = ArrayList<SafetyCenterIssue>()
+ runWithShellPermissionIdentity(automation) {
+ val safetyCenterData = safetyCenterManager!!.safetyCenterData
+ issues.addAll(safetyCenterData.issues)
+ }
+ return issues
+ }
+
+ @JvmStatic
+ fun assertSafetyCenterIssueExist(
+ sourceId: String,
+ issueId: String,
+ issueTypeId: String,
+ automation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ val safetyCenterIssueId = safetyCenterIssueId(sourceId, issueId, issueTypeId)
+ Assert.assertTrue(
+ "Expect issues in safety center",
+ getSafetyCenterIssues(automation).any { safetyCenterIssueId == it.id })
+ }
+
+ @JvmStatic
+ fun assertSafetyCenterIssueDoesNotExist(
+ sourceId: String,
+ issueId: String,
+ issueTypeId: String,
+ automation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ val safetyCenterIssueId = safetyCenterIssueId(sourceId, issueId, issueTypeId)
+ Assert.assertTrue(
+ "Expect no issue in safety center",
+ getSafetyCenterIssues(automation).none { safetyCenterIssueId == it.id })
+ }
+
+ private fun safetyCenterIssueId(sourceId: String, sourceIssueId: String, issueTypeId: String) =
+ SafetyCenterIds.encodeToString(
+ SafetyCenterIssueId.newBuilder()
+ .setSafetyCenterIssueKey(
+ SafetyCenterIssueKey.newBuilder()
+ .setSafetySourceId(sourceId)
+ .setSafetySourceIssueId(sourceIssueId)
+ .setUserId(UserHandle.myUserId())
+ .build())
+ .setIssueTypeId(issueTypeId)
+ .build())
+}
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index fe9037a..598a59a 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -26,13 +26,19 @@
import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.Process
+import android.os.SystemClock
import android.permission.PermissionManager
import android.provider.DeviceConfig
import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
import android.server.wm.WindowManagerStateHelper
import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.StaleObjectException
import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.UiObject2
import android.support.test.uiautomator.UiSelector
+import androidx.annotation.RequiresApi
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.DisableAnimationRule
@@ -40,6 +46,7 @@
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
@@ -50,7 +57,6 @@
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -102,7 +108,7 @@
private val safetyCenterEnabled = callWithShellPermissionIdentity {
DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
- SAFETY_CENTER_ENABLED, false.toString())
+ SAFETY_CENTER_ENABLED, false.toString())
}
@Before
@@ -115,7 +121,7 @@
context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 1800000L
)
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
- SAFETY_CENTER_ENABLED, false.toString(), false)
+ SAFETY_CENTER_ENABLED, false.toString(), false)
}
if (!isScreenOn) {
@@ -207,9 +213,7 @@
testCameraAndMicIndicator(useMic = false, useCamera = true, chainUsage = true)
}
- // Enable when safety center sends a broadcast on safety center flag value change
@Test
- @Ignore
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
fun testSafetyCenterCameraIndicator() {
assumeFalse(isTv)
@@ -217,28 +221,27 @@
val manager = context.getSystemService(CameraManager::class.java)!!
assumeTrue(manager.cameraIdList.isNotEmpty())
changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
testCameraAndMicIndicator(useMic = false, useCamera = true, safetyCenterEnabled = true)
}
- // Enable when safety center sends a broadcast on safety center flag value change
@Test
- @Ignore
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
fun testSafetyCenterMicIndicator() {
assumeFalse(isTv)
assumeFalse(isCar)
changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
testCameraAndMicIndicator(useMic = true, useCamera = false, safetyCenterEnabled = true)
}
- // Enable when safety center sends a broadcast on safety center flag value change
@Test
- @Ignore
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
fun testSafetyCenterHotwordIndicatorBehavior() {
assumeFalse(isTv)
assumeFalse(isCar)
changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
testCameraAndMicIndicator(
useMic = false,
useCamera = false,
@@ -247,14 +250,13 @@
)
}
- // Enable when safety center sends a broadcast on safety center flag value change
@Test
- @Ignore
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
fun testSafetyCenterChainUsageWithOtherUsage() {
assumeFalse(isTv)
assumeFalse(isCar)
changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
testCameraAndMicIndicator(
useMic = false,
useCamera = true,
@@ -295,7 +297,7 @@
// indicator
uiDevice.openQuickSettings()
assertPrivacyChipAndIndicatorsPresent(
- useMic,
+ useMic || useHotword,
useCamera,
chainUsage,
safetyCenterEnabled
@@ -390,13 +392,11 @@
chainUsage: Boolean,
safetyCenterEnabled: Boolean = false
) {
- // Ensure the privacy chip is present (or not)
- val chipFound = isChipPresent()
- if (useMic || useCamera) {
- assertTrue("Did not find chip", chipFound)
- } else { // hotword
- assertFalse("Found chip, but did not expect to", chipFound)
- return
+ // Ensure the privacy chip is present
+ eventually {
+ val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
+ assertTrue("view with id $PRIVACY_CHIP_ID not found", privacyChip.exists())
+ privacyChip.click()
}
eventually {
@@ -405,15 +405,23 @@
return@eventually
}
if (useMic) {
- val iconView = uiDevice.findObject(UiSelector().descriptionContains(micLabel))
- assertTrue("View with description $micLabel not found", iconView.exists())
+ var iconView = if (safetyCenterEnabled) {
+ waitFindObjectOrNull(By.text(micLabel))
+ } else {
+ uiDevice.findObject(UiSelector().descriptionContains(micLabel))
+ }
+ assertNotNull("View with description $micLabel not found", iconView)
}
if (useCamera) {
- val iconView = uiDevice.findObject(UiSelector().descriptionContains(cameraLabel))
- assertTrue("View with text $APP_LABEL not found", iconView.exists())
+ var iconView = if (safetyCenterEnabled) {
+ waitFindObjectOrNull(By.text(cameraLabel))
+ } else {
+ uiDevice.findObject(UiSelector().descriptionContains(cameraLabel))
+ }
+ assertNotNull("View with text $APP_LABEL not found", iconView)
}
- val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
- assertTrue("View with text $APP_LABEL not found", appView.exists())
+ var appView = waitFindObjectOrNull(By.textContains(APP_LABEL))
+ assertNotNull("View with text $APP_LABEL not found", appView)
if (safetyCenterEnabled) {
assertTrue("Did not find safety center views",
uiDevice.findObjects(By.res(SAFETY_CENTER_ITEM_ID)).size > 0)
@@ -445,31 +453,21 @@
"Did not find shell package"
}
- val usageViews = if (safetyCenterEnabled) {
- uiDevice.findObjects(By.res(SAFETY_CENTER_ITEM_ID))
+ if (safetyCenterEnabled) {
+ var micView = waitFindObjectOrNull(By.text(micLabel))
+ assertNotNull("View with text $micLabel not found", micView)
+ var camView = waitFindObjectOrNull(By.text(cameraLabel))
+ assertNotNull("View with text $cameraLabel not found", camView)
+ var shellView = waitFindObjectOrNull(By.textContains(shellLabel))
+ assertNotNull("View with text $shellLabel not found", shellView)
} else {
- uiDevice.findObjects(By.res(PRIVACY_ITEM_ID))
+ val usageViews = uiDevice.findObjects(By.res(PRIVACY_ITEM_ID))
+ assertEquals("Expected two usage views", 2, usageViews.size)
+ val appViews = uiDevice.findObjects(By.textContains(APP_LABEL))
+ assertEquals("Expected two $APP_LABEL view", 2, appViews.size)
+ val shellView = uiDevice.findObjects(By.textContains(shellLabel))
+ assertEquals("Expected only one shell view", 1, shellView.size)
}
- assertEquals("Expected two usage views", 2, usageViews.size)
- val appViews = uiDevice.findObjects(By.textContains(APP_LABEL))
- assertEquals("Expected two $APP_LABEL view", 2, appViews.size)
- val shellView = uiDevice.findObjects(By.textContains(shellLabel))
- assertEquals("Expected only one shell view", 1, shellView.size)
- }
-
- private fun isChipPresent(): Boolean {
- var chipFound = false
- try {
- eventually {
- val privacyChip = uiDevice.findObject(By.res(PRIVACY_CHIP_ID))
- assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip)
- privacyChip.click()
- chipFound = true
- }
- } catch (e: Exception) {
- // Handle more gracefully after
- }
- return chipFound
}
private fun pressBack() {
@@ -488,7 +486,38 @@
private fun changeSafetyCenterFlag(safetyCenterEnabled: String) {
runWithShellPermissionIdentity {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
- SAFETY_CENTER_ENABLED, safetyCenterEnabled, false)
+ SAFETY_CENTER_ENABLED, safetyCenterEnabled, false)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private fun assumeSafetyCenterEnabled() {
+ val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
+ val isSafetyCenterEnabled: Boolean = runWithShellPermissionIdentity<Boolean> {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+ assumeTrue(isSafetyCenterEnabled)
+ }
+
+ protected fun waitFindObjectOrNull(selector: BySelector): UiObject2? {
+ waitForIdle()
+ return findObjectWithRetry({ t -> UiAutomatorUtils.waitFindObjectOrNull(selector, t) })
+ }
+
+ private fun findObjectWithRetry(
+ automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+ timeoutMillis: Long = TIMEOUT_MILLIS
+ ): UiObject2? {
+ waitForIdle()
+ val startTime = SystemClock.elapsedRealtime()
+ return try {
+ automatorMethod(timeoutMillis)
+ } catch (e: StaleObjectException) {
+ val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+ if (remainingTime <= 0) {
+ throw e
+ }
+ automatorMethod(remainingTime)
}
}
}
\ No newline at end of file
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 26b5b7f..d063b40 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
@@ -629,7 +629,7 @@
* then the attach/destroy will not correspond to enable/disable and will not result in a new
* MAC address being generated.
*/
- public void testAttachDiscoveryAddressChanges() {
+ public void testAttachDiscoveryAddressChanges() throws InterruptedException {
if (!TestUtils.shouldTestWifiAware(getContext())) {
return;
}
@@ -638,6 +638,7 @@
Set<TestUtils.MacWrapper> macs = new HashSet<>();
for (int i = 0; i < numIterations; ++i) {
+ Thread.sleep(1000);
AttachCallbackTest attachCb = new AttachCallbackTest();
IdentityChangedListenerTest identityL = new IdentityChangedListenerTest();
mWifiAwareManager.attach(attachCb, identityL, mHandler);
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index 02193c3..7f679ff 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -786,6 +786,7 @@
// be the primary connection.
if (mWifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported()) {
assertThat(wifiInfo.isPrimary()).isFalse();
+ assertConnectionEquals(network, mWifiManager.getConnectionInfo());
} else {
assertThat(wifiInfo.isPrimary()).isTrue();
}
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 3b004dd..a054aa5 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -1880,6 +1880,7 @@
TestExecutor executor = new TestExecutor();
TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ setWifiEnabled(false);
boolean wifiEnabled = mWifiManager.isWifiEnabled();
try {
uiAutomation.adoptShellPermissionIdentity();