diff options
Diffstat (limited to 'tests')
290 files changed, 14054 insertions, 4159 deletions
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java index fa5724ea64bc..ca909a4c9b77 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java @@ -30,7 +30,7 @@ public class DisableScreenshotsActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setDisablePreviewScreenshots(true); + setRecentsScreenshotEnabled(false); getWindow().getDecorView().setBackgroundColor(Color.RED); } diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp index 4e98f4264e11..f026bea80470 100644 --- a/tests/ApkVerityTest/Android.bp +++ b/tests/ApkVerityTest/Android.bp @@ -24,14 +24,21 @@ package { java_test_host { name: "ApkVerityTest", srcs: ["src/**/*.java"], - libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"], + libs: [ + "tradefed", + "compatibility-tradefed", + "compatibility-host-util", + ], static_libs: [ "block_device_writer_jar", "frameworks-base-hostutils", ], - test_suites: ["general-tests", "vts"], - target_required: [ - "block_device_writer_module", + test_suites: [ + "general-tests", + "vts", + ], + data_device_bins_both: [ + "block_device_writer", ], data: [ ":ApkVerityTestCertDer", diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml index 55704eda905e..39b75cc27acb 100644 --- a/tests/ApkVerityTest/AndroidTest.xml +++ b/tests/ApkVerityTest/AndroidTest.xml @@ -31,10 +31,18 @@ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> - <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> <option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" /> </target_preparer> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <!-- The build system produces both 32 and 64 bit variants with bitness suffix. Let + FilePusher find the filename with bitness and push to a remote name without bitness. + --> + <option name="append-bitness" value="true" /> + <option name="cleanup" value="true" /> + <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> + </target_preparer> + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > <option name="jar" value="ApkVerityTest.jar" /> </test> diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp index 0b5f0f611916..0002447d17f2 100644 --- a/tests/ApkVerityTest/block_device_writer/Android.bp +++ b/tests/ApkVerityTest/block_device_writer/Android.bp @@ -24,12 +24,7 @@ package { } cc_test { - // Depending on how the test runs, the executable may be uploaded to different location. - // Before the bug in the file pusher is fixed, workaround by making the name unique. - // See b/124718249#comment12. - name: "block_device_writer_module", - stem: "block_device_writer", - + name: "block_device_writer", srcs: ["block_device_writer.cpp"], cflags: [ "-D_FILE_OFFSET_BITS=64", @@ -38,31 +33,33 @@ cc_test { "-Wextra", "-g", ], - shared_libs: ["libbase", "libutils"], - // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when - // the uploader does not pick up the executable from correct output location. The following - // workaround allows the test to: - // * upload the 32-bit exectuable for both 32 and 64 bits devices to use - // * refer to the same executable name in Java - // * no need to force the Java test to be archiecture specific. - // - // See b/145573317 for details. + shared_libs: [ + "libbase", + "libutils", + ], + compile_multilib: "both", multilib: { lib32: { - suffix: "", + suffix: "32", }, lib64: { - suffix: "64", // not really used + suffix: "64", }, }, auto_gen_config: false, - test_suites: ["general-tests", "pts", "vts"], + test_suites: [ + "general-tests", + "vts", + ], gtest: false, } java_library_host { name: "block_device_writer_jar", srcs: ["src/**/*.java"], - libs: ["tradefed", "junit"], + libs: [ + "tradefed", + "junit", + ], } diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java index 5c2c15b22bb0..9be02ec3be86 100644 --- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java +++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java @@ -32,11 +32,12 @@ import java.util.ArrayList; * <p>To use this class, please push block_device_writer binary to /data/local/tmp. * 1. In Android.bp, add: * <pre> - * target_required: ["block_device_writer_module"], + * data_device_bins_both: ["block_device_writer"], * </pre> * 2. In AndroidText.xml, add: * <pre> - * <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + * <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + * <option name="append-bitness" value="true" /> * <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> * </target_preparer> * </pre> diff --git a/tests/AppLaunchWear/AndroidManifest.xml b/tests/AppLaunchWear/AndroidManifest.xml deleted file mode 100644 index 7dfd7bafbaaa..000000000000 --- a/tests/AppLaunchWear/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.applaunch" - android:sharedUserId="android.uid.system" > - - <uses-permission android:name="android.permission.REAL_GET_TASKS" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - - <uses-sdk - android:minSdkVersion="22" - android:targetSdkVersion="24" /> - - <instrumentation android:label="Measure app start up time" - android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.tests.applaunch" /> - - <application android:label="App Launch Test"> - <uses-library android:name="android.test.runner" /> - </application> -</manifest> diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java deleted file mode 100644 index 97701c61011e..000000000000 --- a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java +++ /dev/null @@ -1,864 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.tests.applaunch; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.ActivityManager; -import android.app.ActivityManager.ProcessErrorStateInfo; -import android.app.IActivityManager; -import android.app.UiAutomation; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.UserHandle; -import android.test.InstrumentationTestCase; -import android.test.InstrumentationTestRunner; -import android.util.Log; - -import androidx.test.rule.logging.AtraceLogger; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * This test is intended to measure the time it takes for the apps to start. - * Names of the applications are passed in command line, and the - * test starts each application, and reports the start up time in milliseconds. - * The instrumentation expects the following key to be passed on the command line: - * apps - A list of applications to start and their corresponding result keys - * in the following format: - * -e apps <app name>^<result key>|<app name>^<result key> - */ -public class AppLaunch extends InstrumentationTestCase { - - private static final int JOIN_TIMEOUT = 10000; - private static final String TAG = AppLaunch.class.getSimpleName(); - - // optional parameter: comma separated list of required account types before proceeding - // with the app launch - private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; - private static final String KEY_APPS = "apps"; - private static final String KEY_TRIAL_LAUNCH = "trial_launch"; - private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; - private static final String KEY_LAUNCH_ORDER = "launch_order"; - private static final String KEY_DROP_CACHE = "drop_cache"; - private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd"; - private static final String KEY_SIMPLEPERF_APP = "simpleperf_app"; - private static final String KEY_TRACE_ITERATIONS = "trace_iterations"; - private static final String KEY_LAUNCH_DIRECTORY = "launch_directory"; - private static final String KEY_TRACE_DIRECTORY = "trace_directory"; - private static final String KEY_TRACE_CATEGORY = "trace_categories"; - private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize"; - private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; - private static final String KEY_COMPILER_FILTERS = "compiler_filters"; - - private static final String SIMPLEPERF_APP_CMD = - "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; - private static final String WEARABLE_ACTION_GOOGLE = - "com.google.android.wearable.action.GOOGLE"; - private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle - private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches - private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; // 5s between launching apps - private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; - private static final String LAUNCH_FILE = "applaunch.txt"; - private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; - private static final String DEFAULT_TRACE_CATEGORIES = - "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm"; - private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; - private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; - private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH"; - private static final String DELIMITER = ","; - private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; - private static final String APP_LAUNCH_CMD = "am start -W -n"; - private static final String SUCCESS_MESSAGE = "Status: ok"; - private static final String WARNING_MESSAGE = "Warning: Activity not started"; - private static final String COMPILE_SUCCESS = "Success"; - private static final String THIS_TIME = "ThisTime:"; - private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d"; - private static final String TRACE_ITERATION = "TRACE_ITERATION-%d"; - private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; - private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION"; - private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; - private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; - private static final String COMPILE_CMD = "cmd package compile -f -m %s %s"; - private static final String SPEED_PROFILE_FILTER = "speed-profile"; - private static final String VERIFY_FILTER = "verify"; - private static final String LAUNCH_SCRIPT_NAME = "appLaunch"; - private static final String WEARABLE_HOME_PACKAGE = "com.google.android.wearable.app"; - - private Map<String, Intent> mNameToIntent; - private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); - private Map<String, String> mNameToResultKey; - private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; - private IActivityManager mAm; - private String mSimplePerfCmd = null; - private String mLaunchOrder = null; - private boolean mDropCache = false; - private int mLaunchIterations = 10; - private int mTraceLaunchCount = 0; - private String mTraceDirectoryStr = null; - private Bundle mResult = new Bundle(); - private Set<String> mRequiredAccounts; - private boolean mTrialLaunch = false; - private BufferedWriter mBufferedWriter = null; - private boolean mSimplePerfAppOnly = false; - private String[] mCompilerFilters = null; - - @Override - protected void setUp() throws Exception { - super.setUp(); - getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); - } - - @Override - protected void tearDown() throws Exception { - getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE); - super.tearDown(); - } - - private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) { - mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result); - } - - private boolean hasFailureOnFirstLaunch(LaunchOrder launch) { - List<AppLaunchResult> results = - mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()); - return (results.size() > 0) && (results.get(0).mLaunchTime < 0); - } - - public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException, - IOException, InterruptedException { - InstrumentationTestRunner instrumentation = - (InstrumentationTestRunner)getInstrumentation(); - Bundle args = instrumentation.getArguments(); - mAm = ActivityManager.getService(); - String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); - - createMappings(); - parseArgs(args); - checkAccountSignIn(); - - // Root directory for applaunch file to log the app launch output - // Will be useful in case of simpleperf command is used - File launchRootDir = null; - if (null != launchDirectory && !launchDirectory.isEmpty()) { - launchRootDir = new File(launchDirectory); - if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { - throw new IOException("Unable to create the destination directory"); - } - } - - try { - File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); - - if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { - throw new IOException("Unable to create the lauch file sub directory"); - } - File file = new File(launchSubDir, LAUNCH_FILE); - FileOutputStream outputStream = new FileOutputStream(file); - mBufferedWriter = new BufferedWriter(new OutputStreamWriter( - outputStream)); - - // Root directory for trace file during the launches - File rootTrace = null; - File rootTraceSubDir = null; - int traceBufferSize = 0; - int traceDumpInterval = 0; - Set<String> traceCategoriesSet = null; - if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) { - rootTrace = new File(mTraceDirectoryStr); - if (!rootTrace.exists() && !rootTrace.mkdirs()) { - throw new IOException("Unable to create the trace directory"); - } - rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY); - if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) { - throw new IOException("Unable to create the trace sub directory"); - } - assertNotNull("Trace iteration parameter is mandatory", - args.getString(KEY_TRACE_ITERATIONS)); - mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS)); - String traceCategoriesStr = args - .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES); - traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE, - DEFAULT_TRACE_BUFFER_SIZE)); - traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL, - DEFAULT_TRACE_DUMP_INTERVAL)); - traceCategoriesSet = new HashSet<String>(); - if (!traceCategoriesStr.isEmpty()) { - String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER); - for (int i = 0; i < traceCategoriesSplit.length; i++) { - traceCategoriesSet.add(traceCategoriesSplit[i]); - } - } - } - - // Get the app launch order based on launch order, trial launch, - // launch iterations and trace iterations - setLaunchOrder(); - - for (LaunchOrder launch : mLaunchOrderList) { - if (mNameToIntent.get(launch.getApp()) == null) { - continue; - } - dropCache(); - String appPkgName = mNameToIntent.get(launch.getApp()) - .getComponent().getPackageName(); - Log.v(TAG, String.format("\nApp name: %s", launch.getApp())); - Log.v(TAG, String.format("Adding app package name: %s", appPkgName)); - // App launch times for trial launch will not be used for final - // launch time calculations. - if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) { - // In the "applaunch.txt" file, trail launches is referenced using - // "TRIAL_LAUNCH" - Log.v(TAG, "Trial Launch"); - if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { - assertTrue(String.format("Not able to compile the app : %s", appPkgName), - compileApp(VERIFY_FILTER, appPkgName)); - } else if (launch.getCompilerFilter() != null) { - assertTrue(String.format("Not able to compile the app : %s", appPkgName), - compileApp(launch.getCompilerFilter(), appPkgName)); - } - // We only need to run a trial for the speed-profile filter, but we always - // run one for "applaunch.txt" consistency. - AppLaunchResult launchResult = null; - if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) { - Log.v(TAG, "Home package detected. Not killing app"); - launchResult = startApp(launch.getApp(), false, launch.getLaunchReason()); - } else { - Log.v(TAG, "Will kill app before launch"); - launchResult = startApp(launch.getApp(), true, launch.getLaunchReason()); - } - if (launchResult.mLaunchTime < 0) { - addLaunchResult(launch, new AppLaunchResult()); - // simply pass the app if launch isn't successful - // error should have already been logged by startApp - continue; - } - sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); - if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { - // Send SIGUSR1 to force dumping a profile. - String sendSignalCommand = - String.format("killall -s SIGUSR1 %s", appPkgName); - getInstrumentation().getUiAutomation().executeShellCommand( - sendSignalCommand); - assertTrue(String.format("Not able to compile the app : %s", appPkgName), - compileApp(launch.getCompilerFilter(), appPkgName)); - } - } - - // App launch times used for final calculation - else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { - Log.v(TAG, "Launch iteration prefix."); - AppLaunchResult launchResults = null; - if (hasFailureOnFirstLaunch(launch)) { - // skip if the app has failures while launched first - continue; - } - // In the "applaunch.txt" file app launches are referenced using - // "LAUNCH_ITERATION - ITERATION NUM" - if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) { - Log.v(TAG, "Home package detected. Not killing app"); - launchResults = startApp(launch.getApp(), false, launch.getLaunchReason()); - } else { - Log.v(TAG, "Will kill app before launch"); - launchResults = startApp(launch.getApp(), true, launch.getLaunchReason()); - } - if (launchResults.mLaunchTime < 0) { - addLaunchResult(launch, new AppLaunchResult()); - // if it fails once, skip the rest of the launches - continue; - } else { - addLaunchResult(launch, launchResults); - } - sleep(POST_LAUNCH_IDLE_TIMEOUT); - } - - // App launch times for trace launch will not be used for final - // launch time calculations. - else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { - Log.v(TAG, "Trace iteration prefix"); - AtraceLogger atraceLogger = AtraceLogger - .getAtraceLoggerInstance(getInstrumentation()); - // Start the trace - try { - atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, - traceDumpInterval, rootTraceSubDir, - String.format("%s-%s", launch.getApp(), launch.getLaunchReason())); - if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) { - Log.v(TAG, "Home package detected. Not killing app"); - startApp(launch.getApp(), false, launch.getLaunchReason()); - } else { - Log.v(TAG, "Will kill app before launch"); - startApp(launch.getApp(), true, launch.getLaunchReason()); - } - sleep(POST_LAUNCH_IDLE_TIMEOUT); - } finally { - // Stop the trace - atraceLogger.atraceStop(); - } - } - closeApp(launch.getApp(), true); - sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); - } - } finally { - if (null != mBufferedWriter) { - mBufferedWriter.close(); - } - } - - for (String app : mNameToResultKey.keySet()) { - for (String compilerFilter : mCompilerFilters) { - StringBuilder launchTimes = new StringBuilder(); - StringBuilder cpuCycles = new StringBuilder(); - StringBuilder majorFaults = new StringBuilder(); - for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) { - launchTimes.append(result.mLaunchTime); - launchTimes.append(","); - if (mSimplePerfAppOnly) { - cpuCycles.append(result.mCpuCycles); - cpuCycles.append(","); - majorFaults.append(result.mMajorFaults); - majorFaults.append(","); - } - } - String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter); - mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString()); - if (mSimplePerfAppOnly) { - mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles", - cpuCycles.toString()); - mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults", - majorFaults.toString()); - } - } - } - instrumentation.sendStatus(0, mResult); - } - - /** - * Compile the app package using compilerFilter and return true or false - * based on status of the compilation command. - */ - private boolean compileApp(String compilerFilter, String appPkgName) throws IOException { - try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). - executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName)); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( - new FileInputStream(result.getFileDescriptor())))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - if (line.contains(COMPILE_SUCCESS)) { - return true; - } - } - return false; - } - } - - /** - * If launch order is "cyclic" then apps will be launched one after the - * other for each iteration count. - * If launch order is "sequential" then each app will be launched for given number - * iterations at once before launching the other apps. - */ - private void setLaunchOrder() { - if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { - for (String compilerFilter : mCompilerFilters) { - if (mTrialLaunch) { - for (String app : mNameToResultKey.keySet()) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); - } - } - for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { - for (String app : mNameToResultKey.keySet()) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(LAUNCH_ITERATION, launchCount))); - } - } - if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { - for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { - for (String app : mNameToResultKey.keySet()) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(TRACE_ITERATION, traceCount))); - } - } - } - } - } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { - for (String compilerFilter : mCompilerFilters) { - for (String app : mNameToResultKey.keySet()) { - if (mTrialLaunch) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); - } - for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(LAUNCH_ITERATION, launchCount))); - } - if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { - for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(TRACE_ITERATION, traceCount))); - } - } - } - } - } else { - assertTrue("Launch order is not valid parameter", false); - } - } - - private void dropCache() { - if (mDropCache) { - assertNotNull("Issue in dropping the cache", - getInstrumentation().getUiAutomation() - .executeShellCommand(DROP_CACHE_SCRIPT)); - } - } - - private void parseArgs(Bundle args) { - mNameToResultKey = new LinkedHashMap<String, String>(); - mNameToLaunchTime = new HashMap<>(); - String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); - if (launchIterations != null) { - mLaunchIterations = Integer.parseInt(launchIterations); - } - String appList = args.getString(KEY_APPS); - if (appList == null) - return; - - String appNames[] = appList.split("\\|"); - for (String pair : appNames) { - String[] parts = pair.split("\\^"); - if (parts.length != 2) { - Log.e(TAG, "The apps key is incorrectly formatted"); - fail(); - } - - mNameToResultKey.put(parts[0], parts[1]); - mNameToLaunchTime.put(parts[0], null); - } - String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); - if (requiredAccounts != null) { - mRequiredAccounts = new HashSet<String>(); - for (String accountType : requiredAccounts.split(",")) { - mRequiredAccounts.add(accountType); - } - } - - String compilerFilterList = args.getString(KEY_COMPILER_FILTERS); - if (compilerFilterList != null) { - // If a compiler filter is passed, we make a trial launch to force compilation - // of the apps. - mTrialLaunch = true; - mCompilerFilters = compilerFilterList.split("\\|"); - } else { - // Just pass a null compiler filter to use the current state of the app. - mCompilerFilters = new String[1]; - } - - // Pre-populate the results map to avoid null checks. - for (String app : mNameToLaunchTime.keySet()) { - HashMap<String, List<AppLaunchResult>> map = new HashMap<>(); - mNameToLaunchTime.put(app, map); - for (String compilerFilter : mCompilerFilters) { - map.put(compilerFilter, new ArrayList<>()); - } - } - - mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); - mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); - mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD); - mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); - mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP)); - mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH)); - - if (mSimplePerfCmd != null && mSimplePerfAppOnly) { - Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s", - KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP)); - } - } - - private boolean hasLeanback(Context context) { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); - } - - private void createMappings() { - mNameToIntent = new LinkedHashMap<String, Intent>(); - - PackageManager pm = getInstrumentation().getContext() - .getPackageManager(); - Intent intentToResolve = new Intent(Intent.ACTION_MAIN); - intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ? - Intent.CATEGORY_LEANBACK_LAUNCHER : - Intent.CATEGORY_LAUNCHER); - List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); - resolveLoop(ris, intentToResolve, pm); - // For Wear - intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE); - ris = pm.queryIntentActivities(intentToResolve, 0); - resolveLoop(ris, intentToResolve, pm); - } - - private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) { - if (ris == null || ris.isEmpty()) { - Log.i(TAG, "Could not find any apps"); - } else { - for (ResolveInfo ri : ris) { - Intent startIntent = new Intent(intentToResolve); - startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - startIntent.setClassName(ri.activityInfo.packageName, - ri.activityInfo.name); - String appName = ri.loadLabel(pm).toString(); - if (appName != null) { - // Support launching intent using package name or app name - mNameToIntent.put(ri.activityInfo.packageName, startIntent); - mNameToIntent.put(appName, startIntent); - } - } - } - } - - private AppLaunchResult startApp(String appName, boolean forceStopBeforeLaunch, - String launchReason) throws NameNotFoundException, RemoteException { - Log.i(TAG, "Starting " + appName); - - Intent startIntent = mNameToIntent.get(appName); - if (startIntent == null) { - Log.w(TAG, "App does not exist: " + appName); - mResult.putString(mNameToResultKey.get(appName), "App does not exist"); - return new AppLaunchResult(); - } - AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch, - launchReason); - Thread t = new Thread(runnable); - t.start(); - try { - t.join(JOIN_TIMEOUT); - } catch (InterruptedException e) { - // ignore - } - return runnable.getResult(); - } - - private void checkAccountSignIn() { - // ensure that the device has the required account types before starting test - // e.g. device must have a valid Google account sign in to measure a meaningful launch time - // for Gmail - if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) { - return; - } - final AccountManager am = - (AccountManager) getInstrumentation().getTargetContext().getSystemService( - Context.ACCOUNT_SERVICE); - Account[] accounts = am.getAccounts(); - // use set here in case device has multiple accounts of the same type - Set<String> foundAccounts = new HashSet<String>(); - for (Account account : accounts) { - if (mRequiredAccounts.contains(account.type)) { - foundAccounts.add(account.type); - } - } - // check if account type matches, if not, fail test with message on what account types - // are missing - if (mRequiredAccounts.size() != foundAccounts.size()) { - mRequiredAccounts.removeAll(foundAccounts); - StringBuilder sb = new StringBuilder("Device missing these accounts:"); - for (String account : mRequiredAccounts) { - sb.append(' '); - sb.append(account); - } - fail(sb.toString()); - } - } - - private void closeApp(String appName, boolean forceStopApp) { - Intent homeIntent = new Intent(Intent.ACTION_MAIN); - homeIntent.addCategory(Intent.CATEGORY_HOME); - homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - getInstrumentation().getContext().startActivity(homeIntent); - sleep(POST_LAUNCH_IDLE_TIMEOUT); - if (forceStopApp) { - Intent startIntent = mNameToIntent.get(appName); - if (startIntent != null) { - String packageName = startIntent.getComponent().getPackageName(); - try { - mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); - } catch (RemoteException e) { - Log.w(TAG, "Error closing app", e); - } - } - } - } - - private void sleep(int time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - // ignore - } - } - - private void reportError(String appName, String processName) { - ActivityManager am = (ActivityManager) getInstrumentation() - .getContext().getSystemService(Context.ACTIVITY_SERVICE); - List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); - if (crashes != null) { - for (ProcessErrorStateInfo crash : crashes) { - if (!crash.processName.equals(processName)) - continue; - - Log.w(TAG, appName + " crashed: " + crash.shortMsg); - mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); - return; - } - } - - mResult.putString(mNameToResultKey.get(appName), - "Crashed for unknown reason"); - Log.w(TAG, appName - + " not found in process list, most likely it is crashed"); - } - - private class LaunchOrder { - private String mApp; - private String mCompilerFilter; - private String mLaunchReason; - - LaunchOrder(String app, String compilerFilter, String launchReason){ - mApp = app; - mCompilerFilter = compilerFilter; - mLaunchReason = launchReason; - } - - public String getApp() { - return mApp; - } - - public void setApp(String app) { - mApp = app; - } - - public String getCompilerFilter() { - return mCompilerFilter; - } - - public String getLaunchReason() { - return mLaunchReason; - } - - public void setLaunchReason(String launchReason) { - mLaunchReason = launchReason; - } - } - - private class AppLaunchResult { - long mLaunchTime; - long mCpuCycles; - long mMajorFaults; - - AppLaunchResult() { - mLaunchTime = -1L; - mCpuCycles = -1L; - mMajorFaults = -1L; - } - - AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) { - try { - mLaunchTime = Long.parseLong(launchTime, 10); - mCpuCycles = Long.parseLong(cpuCycles, 10); - mMajorFaults = Long.parseLong(majorFaults, 10); - } catch (NumberFormatException e) { - Log.e(TAG, "Error parsing result", e); - } - } - } - - private class AppLaunchRunnable implements Runnable { - private Intent mLaunchIntent; - private AppLaunchResult mLaunchResult; - private boolean mForceStopBeforeLaunch; - private String mLaunchReason; - - public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch, - String launchReason) { - mLaunchIntent = intent; - mForceStopBeforeLaunch = forceStopBeforeLaunch; - mLaunchReason = launchReason; - mLaunchResult = new AppLaunchResult(); - } - - public AppLaunchResult getResult() { - return mLaunchResult; - } - - public void run() { - File launchFile = null; - try { - String packageName = mLaunchIntent.getComponent().getPackageName(); - String componentName = mLaunchIntent.getComponent().flattenToShortString(); - if (mForceStopBeforeLaunch) { - Log.v(TAG, "Stopping app before launch"); - mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); - } else { - Log.v(TAG, "Not killing app. Going to Home Screen."); - ParcelFileDescriptor goHome = getInstrumentation().getUiAutomation() - .executeShellCommand("input keyevent 3"); - } - String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); - if (mSimplePerfAppOnly) { - try { - // executeShellCommand cannot handle shell specific actions, like '&'. - // Therefore, we create a file containing the command and make that - // the command to launch. - launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh"); - launchFile.setExecutable(true); - try (FileOutputStream stream = new FileOutputStream(launchFile); - BufferedWriter writer = - new BufferedWriter(new OutputStreamWriter(stream))) { - String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd); - writer.write(cmd); - } - launchCmd = launchFile.getAbsolutePath(); - } catch (IOException e) { - Log.w(TAG, "Error writing the launch command", e); - return; - } - } else if (null != mSimplePerfCmd) { - launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); - } - Log.v(TAG, "Final launch cmd:" + launchCmd); - ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() - .executeShellCommand(launchCmd); - mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format - ("App Launch :%s %s", componentName, mLaunchReason)); - } catch (RemoteException e) { - Log.w(TAG, "Error launching app", e); - } finally { - if (launchFile != null) { - launchFile.delete(); - } - } - } - - /** - * Method to parse the launch time info and write the result to file - * - * @param parcelDesc - * @return - */ - private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, - String headerInfo) { - String launchTime = "-1"; - String cpuCycles = "-1"; - String majorFaults = "-1"; - boolean launchSuccess = false; - try { - InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); - /* SAMPLE OUTPUT : - Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } - Status: ok - Activity: com.google.android.calculator/com.android.calculator2.Calculator - ThisTime: 357 - TotalTime: 357 - WaitTime: 377 - Complete*/ - /* WHEN NOT KILLING HOME : - Starting: Intent { cmp=com.google.android.wearable.app/ - com.google.android.clockwork.home.calendar.AgendaActivity } - Warning: Activity not started, its current task has been brought to the front - Status: ok - Activity: com.google.android.wearable.app/ - com.google.android.clockwork.home.calendar.AgendaActivity - ThisTime: 209 - TotalTime: 209 - WaitTime: 285 - Complete*/ - /* WITH SIMPLEPERF : - Performance counter statistics, - 6595722690,cpu-cycles,4.511040,GHz,(100%), - 0,major-faults,0.000,/sec,(100%), - Total test time,1.462129,seconds,*/ - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( - inputStream)); - String line = null; - int lineCount = 1; - int addLineForWarning = 0; - mBufferedWriter.newLine(); - mBufferedWriter.write(headerInfo); - mBufferedWriter.newLine(); - while ((line = bufferedReader.readLine()) != null) { - if (lineCount == 2 && line.contains(WARNING_MESSAGE)) { - addLineForWarning = 1; - } - if (lineCount == (2 + addLineForWarning) && line.contains(SUCCESS_MESSAGE)) { - launchSuccess = true; - } - // Parse TotalTime which is the launch time - if (launchSuccess && lineCount == (5 + addLineForWarning)) { - String launchSplit[] = line.split(":"); - launchTime = launchSplit[1].trim(); - } - - if (mSimplePerfAppOnly) { - // Parse simpleperf output. - if (lineCount == (9 + addLineForWarning)) { - if (!line.contains("cpu-cycles")) { - Log.e(TAG, "Error in simpleperf output"); - } else { - cpuCycles = line.split(",")[0].trim(); - } - } else if (lineCount == (10 + addLineForWarning)) { - if (!line.contains("major-faults")) { - Log.e(TAG, "Error in simpleperf output"); - } else { - majorFaults = line.split(",")[0].trim(); - } - } - } - mBufferedWriter.write(line); - mBufferedWriter.newLine(); - lineCount++; - } - mBufferedWriter.flush(); - inputStream.close(); - } catch (IOException e) { - Log.w(TAG, "Error writing the launch file", e); - } - return new AppLaunchResult(launchTime, cpuCycles, majorFaults); - } - - } -} diff --git a/tests/AttestationVerificationTest/Android.bp b/tests/AttestationVerificationTest/Android.bp new file mode 100644 index 000000000000..b98f8cb0c21d --- /dev/null +++ b/tests/AttestationVerificationTest/Android.bp @@ -0,0 +1,45 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "AttestationVerificationTest", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + defaults: ["cts_defaults"], + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + platform_apis: true, + certificate: "platform", + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "compatibility-device-util-axt", + "androidx.test.rules", + "androidx.test.ext.junit", + "platform-test-annotations", + "services.core", + ], +} diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml new file mode 100755 index 000000000000..37321ad80b0f --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.attestationverification"> + + <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" /> + <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" /> + + <application> + <uses-library android:name="android.test.runner"/> + <activity android:name=".SystemAttestationVerificationTest$TestActivity" /> + <activity android:name=".PeerDeviceSystemAttestationVerificationTest$TestActivity" /> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.attestationverification"> + </instrumentation> +</manifest> diff --git a/tests/AttestationVerificationTest/AndroidTest.xml b/tests/AttestationVerificationTest/AndroidTest.xml new file mode 100644 index 000000000000..132576035952 --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Platform tests for Attestation Verification Framework"> + <option name="test-tag" value="AttestationVerificationTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="AttestationVerificationTest.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.security.attestationverification" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/AttestationVerificationTest/OWNERS b/tests/AttestationVerificationTest/OWNERS new file mode 100644 index 000000000000..a7a6ef156eda --- /dev/null +++ b/tests/AttestationVerificationTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/security/attestationverification/OWNERS diff --git a/tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem b/tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem new file mode 100644 index 000000000000..e29ff487806e --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem @@ -0,0 +1,81 @@ +-----BEGIN CERTIFICATE----- +MIICkjCCAjmgAwIBAgIBATAKBggqhkjOPQQDAjA5MQwwCgYDVQQMDANURUUxKTAn +BgNVBAUTIDg2ZTQ0MjRhMjY2NDlhZDcyZWZhNWM0MWEwM2IyN2QxMCAXDTcwMDEw +MTAwMDAwMFoYDzIxMDYwMjA3MDYyODE1WjAfMR0wGwYDVQQDDBRBbmRyb2lkIEtl +eXN0b3JlIEtleTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIlTwcvhe+DLV45X +RCTO7HoN20Ib7IbCEhV5+YdMiYOp/0AdKk8oYvsri1XODeC4zcoPfHNdQGt/68i0 +ADbilJmjggFIMIIBRDAOBgNVHQ8BAf8EBAMCB4AwggEwBgorBgEEAdZ5AgERBIIB +IDCCARwCAQMKAQECAQQKAQEECXBsYXllcjQ1NgQAMFe/hT0IAgYBfvkgVei/hUVH +BEUwQzEdMBsEFmNvbS5nb29nbGUuYXR0ZXN0YXRpb24CAQExIgQgOqyVXRJUdAGY +/XVx8y/uRPiebqlyELt1EpqIz29h5tUwgaehCDEGAgECAgEDogMCAQOjBAICAQCl +CDEGAgEEAgEGqgMCAQG/g3cCBQC/hT4DAgEAv4VATDBKBCCEZx8qY8Ys0HC2TqPq +74eYPzh5L/agxD7Bn7zVBQHoNAEB/woBAAQguJwoDfWBjRaedzQ6TJPFJJKs+ytr ++8Vu2CSmqifFBHW/hUEFAgMB1MC/hUIFAgMDFdm/hU4GAgQBNIjJv4VPBgIEATSI +yTAKBggqhkjOPQQDAgNHADBEAiBdGxfMEx59k5+zo+hV3Q9kgjbGi0zU3WH355P5 +JZttBwIgY4FZsSreUJL8RY3JvfvD8BRw8GuXcB1OQ600hwaYYC4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8zCCAXqgAwIBAgIRAOuuukN0OHbNQvKngECkewEwCgYIKoZIzj0EAwIwOTEM +MAoGA1UEDAwDVEVFMSkwJwYDVQQFEyA3MDkxMmRmNDYxMDRmYWFlOTQ3ODY0ZTU4 +MDRmMWY4ZDAeFw0yMDA5MjgyMDI3NTZaFw0zMDA5MjYyMDI3NTZaMDkxDDAKBgNV +BAwMA1RFRTEpMCcGA1UEBRMgODZlNDQyNGEyNjY0OWFkNzJlZmE1YzQxYTAzYjI3 +ZDEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT3Mjl05ewv6G8zAR4fXJy2iadU +yK7rNvzlECy2+nhEieL8BFXDvo0tx5fYs8qr67j/KvluFBfp2r9s+ckWz3Kzo2Mw +YTAdBgNVHQ4EFgQUsVKBzAs1lMXAauQ3mGAJZJqK5tAwHwYDVR0jBBgwFoAUEsQA +i8d2oLULSi5Ix4BTGGbvUEkwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AgQwCgYIKoZIzj0EAwIDZwAwZAIwfFziBCwuM1thLUSUNI61Xx/vnDnNkSv/aX5D +yLjxbLlgnFSzIrc+6vf6h6L/+TjYAjAq6h9GKtMn4R0286MoqYqzp/rHn6JD2sqH +iM8KZ0oA+Ut242EcmGjAoNfGZGZGddQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDkzCCAXugAwIBAgIQNTAX5z3CBac6nD3hQiMDcDANBgkqhkiG9w0BAQsFADAb +MRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MB4XDTIwMDkyODIwMjUwMloXDTMw +MDkyNjIwMjUwMlowOTEMMAoGA1UEDAwDVEVFMSkwJwYDVQQFEyA3MDkxMmRmNDYx +MDRmYWFlOTQ3ODY0ZTU4MDRmMWY4ZDB2MBAGByqGSM49AgEGBSuBBAAiA2IABA/7 +xZFlFtTjdy2B3p7E+FsrBjyhBSqY4a9FywawXMJRSja3HAK36ruzJjWlEkD+D0vq +HI2joY39FHmWoZWwm2cq9gOleFGYOSCpMr4ib7xtq/6nefvKTP5rutxudF97t6Nj +MGEwHQYDVR0OBBYEFBLEAIvHdqC1C0ouSMeAUxhm71BJMB8GA1UdIwQYMBaAFDZh +4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgIEMA0GCSqGSIb3DQEBCwUAA4ICAQAaMONDQxJz3PRn9gHQW5KP+TIoBPJZyGa1 +QFuEBcMDTtIxBxEh5Pj3ivPBc76PrdYu5U47Ve5YYCPsTpUTj7dOxbzGSZjfjvHF +fNwy24g1Lah2iAdQRVErhWKBlpnQhBnnRrrNmTTmzhl8NvSExqAPP746dqwm1kQ7 +YesC5yoEAHpxamhlZpIKAjSxSZeHWace2qV00M8qWd/7lIpqttJjFFrhCjzR0dtr +oIIpC5EtmqIWdLeg6yZjJkX+Cjv4F8mRfBtwuNuxFsfALQ3D5l8WKw3iwPebmCy1 +kEby8Eoq88FxzXQp/XgAaljlrKXyuxptrc1noRuob4g42Oh6wetueYRSCtO6Bkym +0UMnld/kG77aeiHOMVVb86wrhNuAGir1vgDGOBsclITVyuu9ka0YVQjjDm3phTpd +O8JV16gbei2Phn+FfRV1MSDsZo/wu0i2KVzgs27bfJocMHXv+GzvwfefYgMJ/rYq +Bg27lpsWzmFEPv2cyhA5PwwbG8ceswa3RZE/2eS9o7STkz93jr/KsKLcMBY6cX2C +q4CBJByKFJtVANOVj+neFNxc2sQgeTT33yYNKbe4b5bm7Ki1FbrhFVckpzUGDnKs +gL+AxvALWOoryDGwNbJiW8PRiD3HHByiMvSEQ7e7BSc2KjbsaWbCfYZAMZJEhEsc +P1l8lcUVuA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz +NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu +XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U +h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno +L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok +QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA +D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI +mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW +Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91 +oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o +jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB +ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH +ex0SdDrx+tWUDqG8At2JHA== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem b/tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem new file mode 100644 index 000000000000..3d6410af042a --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIGCDCCBHCgAwIBAgIBATANBgkqhkiG9w0BAQsFADApMRkwFwYDVQQFExAyZGM1OGIyZDFhMjQx +MzI2MQwwCgYDVQQMDANURUUwIBcNNzAwMTAxMDAwMDAwWhgPMjEwNjAyMDcwNjI4MTVaMB8xHTAb +BgNVBAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEApNVcnyN40MANMbbo2nMGNq2NNysDSjfLm0W3i6wPKf0ffCYkhWM4dCmQKKf50uAZTBeTit4c +NwXeZn3qellMlOsIN3Qc384rfN/8cikrRvUAgibz0Jy7STykjwa7x6tKwqITxbO8HqAhKo8/BQXU +xzrOdIg5ciy+UM7Vgh7a7ogen0KL2iGgrsalb1ti7Vlzb6vIJ4WzIC3TGD2sCkoPahghwqFDZZCo +/FzaLoNY0jAUX2mL+kf8aUaoxz7xA9FTvgara+1pLBR1s4c8xPS2HdZipcVXWfey0wujv1VAKs4+ +tXjKlHkYBHBBceEjxUtEmrapSQEdpHPv7Xh9Uanq4QIDAQABo4ICwTCCAr0wDgYDVR0PAQH/BAQD +AgeAMIICqQYKKwYBBAHWeQIBEQSCApkwggKVAgEDCgEBAgEECgEBBANhYmMEADCCAc2/hT0IAgYB +ZOYGEYe/hUWCAbsEggG3MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXlj +aGFpbgIBHTAZBBRjb20uYW5kcm9pZC5zZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNl +cwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZp +Y2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNwb3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxv +Y2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci50ZWxlY29tAgEdMCAEG2NvbS5h +bmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNTUmVzdGFydERldGVjdG9y +AgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS5hbmRyb2lkLnBy +b3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcXb9hmqjCB +rqEIMQYCAQICAQOiAwIBAaMEAgIIAKUFMQMCAQSmCDEGAgEDAgEFv4FIBQIDAQABv4N3AgUAv4U+ +AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIHKNsSdP +HxzxVx3kOAsEilVKxKOA529TVQg1KQhKk3gBv4VBAwIBAL+FQgUCAwMUs7+FTgUCAwMUs7+FTwUC +AwMUszANBgkqhkiG9w0BAQsFAAOCAYEAJMIuzdNUdfrE6sIdmsnMn/scSG2odbphj8FkX9JGdF2S +OT599HuDY9qhvkru2Dza4sLKK3f4ViBhuR9lpfeprKvstxbtBO7jkLYfVn0ZRzHRHVEyiW5IVKh+ +qOXVJ9S1lMShOTlsaYJytLKIlcrRAZBEXZiNbzTuVh1CH6X9Ni1dog14snm+lcOeORdL9fht2CHa +u/caRnpWiZbjoAoJp0O89uBrRkXPpln51+3jPY6AFny30grNAvKguauDcPPhNV1yR+ylSsQi2gm3 +Rs4pgtlxFLMfZLgT0cbkl+9zk/QUqlpBP8ftUBsOI0ARr8xhFN3cvq9kXGLtJ9hEP9PRaflAFREk +DK3IBIbVcAFZBFoAQOdE9zy0+F5bQrznPGaZg4Dzhcx33qMDUTgHtWoy+k3ePGQMEtmoTTLgQywW +OIkXEoFqqGi9GKJXUT1KYi5NsigaYqu7FoN4Qsvs61pMUEfZSPP2AFwkA8uNFbmb9uxcxaGHCA8i +3i9VM6yOLIrP +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem b/tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem new file mode 100644 index 000000000000..6d261fae47cf --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFoDCCA4igAwIBAgIQTfpKgAsLZJhp2V4xUriMADANBgkqhkiG9w0BAQ0FADBp +MQswCQYDVQQGEwJVUzEUMBIGA1UECgwLR29vZ2xlIEluYy4xFzAVBgNVBAsMDkFu +ZHJvaWQgVGhpbmdzMSswKQYDVQQDDCJBbmRyb2lkIFRoaW5ncyBBdHRlc3RhdGlv +biBSb290IENBMCAXDTE3MDYyMTIwMjQzN1oYDzIwNTcwNjExMjAyNDM3WjBpMQsw +CQYDVQQGEwJVUzEUMBIGA1UECgwLR29vZ2xlIEluYy4xFzAVBgNVBAsMDkFuZHJv +aWQgVGhpbmdzMSswKQYDVQQDDCJBbmRyb2lkIFRoaW5ncyBBdHRlc3RhdGlvbiBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuO82oerGivb9 +G9bWyM8Pg0y6SOnAC8/8b92dp1v4Npnc+QpjPRUKgn8lzjQ9Jo6IGY3OShRBiQYl +bbZYkfJnC5HtqbOETdPLZclErVE/G6Oda1IeZWvQVMjNImEYOLL5ct2RxiPttd8v +SLyOSNFPf5/SeFqX/La0NcmXMOvPSrTW3qO34brnC+ih7mlpJFLz6Up93N3Umxsl +IElz2wCG72t6k3+caWLyIPVgIPmsQrfTeBK/hN5dAJgAN65BsTevLHRP9J610wj3 +RagSIK1NdTuJRnr5ZyTQrwE2nA8H3IJ7/eo6IlGhXPwLKDhbdxYygPxdlCq6Rl96 +aVLjfpqDPtJ9ow+QKZuEDbYJ4z4olNXC6O5G7vqnCuULA/2E7y7DZObjzXOrdx2z +9YKd8BrIDMTN/5mmw2us8tywiaQhbl8vOtjU+A+iBBnkj/wt9TYyLKopdrDlo5mz +wy5l750HOkVZXC3VkeECnp+9duSHdS4qeUf/W1j9nPM7kY0HFLPUVX9AFVp2JXnC +iKZC32GQAVsDc1iyAZWAVTqA7E0fBHhk9jUnA0W9b5Lq06oW95ngNR1MIFY871i8 +aLHCBpIix8DuMe8NB9spCIP6WCQqGiWQQpzbeuBPtoi424xwZTO4oectTd77bs9V +Rvunn49fz308KnoWjk/by1N7gWyTb38CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMDQ1I0RKwFCI+Fy9uIIJ/HrXuqu +MA0GCSqGSIb3DQEBDQUAA4ICAQB09qkyEpEDocdN5pPeXqtjj9d0AXREUGH2LhnC +z9KZcUFR+JskwEMHCaOENOmKI3zWRmxT7d8cVywwGk+ExE7EBQoeHlh3Yo44M8aj +ZL7RHCvHRYsePhAJkYpJ02IMR60TV+1jhMqE8+BnqFivS7kft4t295EyrnLRZE3b +Nfc0t011j02RwUrioR57mdvS9EZBRnMQkobhn+jWt9O+V3mtplW+1A2n4ec6uni1 +2MMgAWHuO1sKVYd5Sp4JMUpNnfmQAMnNiOMF6VxkpaoF1lZWo4TrLxuDKJG3O8h1 +fByjCpNVY8kOvvYEadbldzh6Agy/3ppb9yfG7X7FtHr1ghNjuNT6w5VgvbRtoRja +/ZSKuJMaKm5emMWNkls/cwVSPJIvTOzPTeYK1BKSyAL2LDJ93HI7x8h79/Q7gKRi +kL8qT7GW2FqpWTK0253sJHqCJJP4A5Rxtf2+Afwqadfc6Ga4jJHb7rPXngz4j1ZB +gl5yjXgWF9wHGxqrjKWe2EA3d47BC4HG3Rf5L56KQiRPhTqTk5vtZwtwLRLFDLt7 +Hdff13O1oLhn+2z9xkASUL3rFE/qWajZP7fk3CvzcuXwKDTZomIC4nNaglx4nLdj +lHhOq+6ON8MZC46sLStD+D4a9A1HOoihJgI/yGGkwdrp4KQIveRkEBO/x9v3NNBE +bMwG9w== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_root_certs.pem b/tests/AttestationVerificationTest/assets/test_root_certs.pem new file mode 100644 index 000000000000..c51851fe3da5 --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_root_certs.pem @@ -0,0 +1,61 @@ +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy +ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD +VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk +Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD +ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB +Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m +qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY +DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm +QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u +JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD +CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy +ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD +qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic +MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1 +wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz +NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu +XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U +h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno +L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok +QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA +D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI +mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW +Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91 +oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o +jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB +ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH +ex0SdDrx+tWUDqG8At2JHA== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem b/tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem new file mode 100644 index 000000000000..282771000bf7 --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIIC7DCCApGgAwIBAgIBATAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCVVMxEzAR +BgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UE +CwwHQW5kcm9pZDE7MDkGA1UEAwwyQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBB +dHRlc3RhdGlvbiBJbnRlcm1lZGlhdGUwHhcNNzAwMTAxMDAwMDAwWhcNNjkxMjMx +MjM1OTU5WjAfMR0wGwYDVQQDDBRBbmRyb2lkIEtleXN0b3JlIEtleTBZMBMGByqG +SM49AgEGCCqGSM49AwEHA0IABEYtCH28qu+St0F0TixVsQz0L/Y7DcRHgYAU98E6 +edwOpACFmmseYxMjvmZv/4jURSG2/Z0J1s3A/qFzIY96/tyjggFSMIIBTjALBgNV +HQ8EBAMCB4AwggEcBgorBgEEAdZ5AgERBIIBDDCCAQgCAQQKAQACASkKAQAECXBs +YXllcjQ1NgQAMIHqoQgxBgIBAgIBA6IDAgEDowQCAgEApQgxBgIBBAIBBqoDAgEB +v4N3AgUAv4U9CAIGAX8DoY9Qv4U+AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAv4VBBQIDAa2wv4VCBQIDAxUbv4VFRwRFMEMxHTAbBBZjb20uZ29v +Z2xlLmF0dGVzdGF0aW9uAgEBMSIEIDqslV0SVHQBmP11cfMv7kT4nm6pchC7dRKa +iM9vYebVMAAwHwYDVR0jBBgwFoAUP/ys1hqxOp6BILjVJRzFZbsekakwCgYIKoZI +zj0EAwIDSQAwRgIhAMzs7gWWBIITpeLeEEx9B8ihdhkFqpMGlsYLRO01ZIOeAiEA +uKs9xfK3fIOpVAhDmsrp+zE8KUwyvqCU/IS13tXz7Ng= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD +VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu +ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx +MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB +bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz +dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue +efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8 +U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R +qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG +AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8 +wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9 +Xvsiu+f+uXc/WT/7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll +dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD +VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw +HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT +BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq +QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59 +dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O +BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W +EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG +SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN +C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt new file mode 100644 index 000000000000..32c2230e4880 --- /dev/null +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt @@ -0,0 +1,161 @@ +package android.security.attestationverification + +import android.app.Activity +import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE +import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE +import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.TYPE_UNKNOWN +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.ByteArrayOutputStream +import java.security.cert.CertificateFactory +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +/** Test for system-defined attestation verifiers. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class PeerDeviceSystemAttestationVerificationTest { + + @get:Rule + val rule = ActivityScenarioRule(TestActivity::class.java) + + private val certifcateFactory = CertificateFactory.getInstance("X.509") + private lateinit var activity: Activity + private lateinit var avm: AttestationVerificationManager + private lateinit var invalidAttestationByteArray: ByteArray + + @Before + fun setup() { + rule.getScenario().onActivity { + avm = it.getSystemService(AttestationVerificationManager::class.java) + activity = it + } + invalidAttestationByteArray = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToByteArray() + } + + @Test + fun verifyAttestation_returnsFailureWrongBindingType() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + avm.verifyAttestation(profile, TYPE_UNKNOWN, Bundle(), + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureEmptyRequirements() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureMismatchBindingType() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val publicKeyRequirements = Bundle() + publicKeyRequirements.putByteArray(PARAM_PUBLIC_KEY, "publicKeyStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_CHALLENGE, publicKeyRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + + val future2 = CompletableFuture<Int>() + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "challengeStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, challengeRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future2.complete(result) + } + + assertThat(future2.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWrongResourceKey() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val wrongKeyRequirements = Bundle() + wrongKeyRequirements.putByteArray("wrongReqKey", "publicKeyStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, wrongKeyRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureEmptyAttestation() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val requirements = Bundle() + requirements.putByteArray(PARAM_PUBLIC_KEY, "publicKeyStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, requirements, ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureTrustAnchorMismatch() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_CHALLENGE, challengeRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + private fun <T> CompletableFuture<T>.getSoon(): T { + return this.get(1, TimeUnit.SECONDS) + } + + private fun String.fromPEMFileToByteArray(): ByteArray { + val certs = certifcateFactory.generateCertificates( + InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() + .open(this)) + val bos = ByteArrayOutputStream() + certs.forEach { + bos.write(it.encoded) + } + return bos.toByteArray() + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } + + companion object { + private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" + } +} diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt new file mode 100644 index 000000000000..169effaa45ca --- /dev/null +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -0,0 +1,222 @@ +package android.security.attestationverification + +import android.os.Bundle +import android.app.Activity +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import com.google.common.truth.Truth.assertThat +import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED +import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN +import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE +import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS +import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import java.lang.IllegalArgumentException +import java.io.ByteArrayOutputStream +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +/** Test for system-defined attestation verifiers. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class SystemAttestationVerificationTest { + @get:Rule + val rule = ActivityScenarioRule(TestActivity::class.java) + + private lateinit var activity: Activity + private lateinit var avm: AttestationVerificationManager + private lateinit var androidKeystore: KeyStore + + @Before + fun setup() { + rule.getScenario().onActivity { + avm = it.getSystemService(AttestationVerificationManager::class.java) + activity = it + androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + } + } + + @Test + fun verifyAttestation_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_UNKNOWN) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyAttestation_returnsFailureWithEmptyAttestation() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithEmptyRequirements() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithWrongBindingType() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, TYPE_PUBLIC_KEY, + selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithWrongRequirements() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + val wrongKeyRequirements = Bundle() + wrongKeyRequirements.putByteArray( + "wrongBindingKey", "challengeStr".encodeToByteArray()) + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithWrongChallenge() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + val wrongChallengeRequirements = Bundle() + wrongChallengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) { + result, _ -> future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED. + @Test + fun verifyAttestation_returnsSuccess() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyToken_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_tooBigMaxAgeThrows() { + val future = CompletableFuture<VerificationToken>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + future.complete(token) + } + + assertThrows(IllegalArgumentException::class.java) { + avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), future.getSoon(), + Duration.ofSeconds(3601)) + } + } + + private fun <T> CompletableFuture<T>.getSoon(): T { + return this.get(1, TimeUnit.SECONDS) + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } + + inner class TestSelfTrustedAttestation(val alias: String, val challenge: String) { + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + val localBindingType = TYPE_CHALLENGE + val requirements: Bundle + val attestation: ByteArray + + init { + val challengeByteArray = challenge.encodeToByteArray() + generateAndStoreKey(alias, challengeByteArray) + attestation = generateCertificatesByteArray(alias) + requirements = Bundle() + requirements.putByteArray(PARAM_CHALLENGE, challengeByteArray) + } + + private fun generateAndStoreKey(alias: String, challenge: ByteArray) { + val kpg: KeyPairGenerator = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_EC, + ANDROID_KEYSTORE + ) + val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder( + alias, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + ).run { + // a challenge results in a generated attestation + setAttestationChallenge(challenge) + setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) + build() + } + kpg.initialize(parameterSpec) + kpg.generateKeyPair() + } + + private fun generateCertificatesByteArray(alias: String): ByteArray { + val pkEntry = androidKeystore.getEntry(alias, null) as KeyStore.PrivateKeyEntry + val certs = pkEntry.certificateChain + val bos = ByteArrayOutputStream() + certs.forEach { + bos.write(it.encoded) + } + return bos.toByteArray() + } + } + + companion object { + private const val TAG = "AVFTEST" + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + } +} diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java b/tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java new file mode 100644 index 000000000000..0d15fe72920a --- /dev/null +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java @@ -0,0 +1,297 @@ +/* + * 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.server.security; + +import static com.google.common.truth.Truth.assertThat; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** Test for data class holding parsed X509Certificate attestation attributes. */ +@RunWith(AndroidJUnit4.class) +public class AndroidKeystoreAttestationVerificationAttributesTest { + @Rule public ExpectedException mException = ExpectedException.none(); + private static final String TEST_PHYSCIAL_DEVICE_CERTS = + "test_attestation_wrong_root_certs.pem"; + private static final String TEST_PHYSICAL_DEVICE_CERTS_2 = + "test_attestation_with_root_certs.pem"; + private static final String TEST_VIRTUAL_DEVICE_CERTS = + "test_virtual_device_attestation_certs.pem"; + private static final String TEST_CERT_NO_ATTESTATION_EXTENSION = + "test_no_attestation_ext_certs.pem"; + private static final String TEST_CERTS_NO_ATTESTATION_EXTENSION_2 = + "test_root_certs.pem"; + + + private CertificateFactory mFactory; + private AndroidKeystoreAttestationVerificationAttributes mPhysicalDeviceAttributes; + private AndroidKeystoreAttestationVerificationAttributes mPhysicalDeviceAttributes2; + private AndroidKeystoreAttestationVerificationAttributes mVirtualDeviceAttributes; + + @Before + public void setUp() throws Exception { + mFactory = CertificateFactory.getInstance("X.509"); + mPhysicalDeviceAttributes = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + generateCertificate(TEST_PHYSCIAL_DEVICE_CERTS)); + mPhysicalDeviceAttributes2 = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + generateCertificates(TEST_PHYSICAL_DEVICE_CERTS_2).get(0)); + mVirtualDeviceAttributes = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + generateCertificates(TEST_VIRTUAL_DEVICE_CERTS).get(0)); + } + + @Test + public void parseCertificate_noAttestationExtension() throws Exception { + List<X509Certificate> certsNoAttestation = + generateCertificates(TEST_CERTS_NO_ATTESTATION_EXTENSION_2); + certsNoAttestation.add(generateCertificate(TEST_CERT_NO_ATTESTATION_EXTENSION)); + for (X509Certificate cert: certsNoAttestation) { + mException.expect(CertificateEncodingException.class); + mException.expectMessage( + CoreMatchers.containsString("No attestation extension found in certificate.")); + + AndroidKeystoreAttestationVerificationAttributes.fromCertificate(cert); + } + } + + @Test + public void parseCertificate_attestationLevel() { + assertThat(mPhysicalDeviceAttributes.getAttestationVersion()).isEqualTo(3); + assertThat(mPhysicalDeviceAttributes2.getAttestationVersion()).isEqualTo(3); + assertThat(mVirtualDeviceAttributes.getAttestationVersion()).isEqualTo(4); + } + + @Test + public void parseCertificate_attestationSecurityLevel() { + assertThat(mPhysicalDeviceAttributes.getAttestationSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mPhysicalDeviceAttributes2.getAttestationSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mVirtualDeviceAttributes.getAttestationSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.SOFTWARE); + } + + @Test + public void parseCertificate_isAttestationHardwareBacked() { + assertThat(mPhysicalDeviceAttributes.isAttestationHardwareBacked()).isTrue(); + assertThat(mPhysicalDeviceAttributes2.isAttestationHardwareBacked()).isTrue(); + assertThat(mVirtualDeviceAttributes.isAttestationHardwareBacked()).isFalse(); + } + + @Test + public void parseCertificate_keymasterLevel() { + assertThat(mPhysicalDeviceAttributes.getKeymasterVersion()).isEqualTo(4); + assertThat(mPhysicalDeviceAttributes2.getKeymasterVersion()).isEqualTo(4); + assertThat(mVirtualDeviceAttributes.getKeymasterVersion()).isEqualTo(41); + } + + @Test + public void parseCertificate_keymasterSecurityLevel() { + assertThat(mPhysicalDeviceAttributes.getKeymasterSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mPhysicalDeviceAttributes2.getKeymasterSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mVirtualDeviceAttributes.getKeymasterSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.SOFTWARE); + } + + @Test + public void parseCertificate_isKeymasterHardwareBacked() { + assertThat(mPhysicalDeviceAttributes.isKeymasterHardwareBacked()).isTrue(); + assertThat(mPhysicalDeviceAttributes2.isKeymasterHardwareBacked()).isTrue(); + assertThat(mVirtualDeviceAttributes.isKeymasterHardwareBacked()).isFalse(); + } + + @Test + public void parseCertificate_attestationChallenge() { + assertThat(mPhysicalDeviceAttributes.getAttestationChallenge().toByteArray()).isEqualTo( + "abc".getBytes(UTF_8)); + assertThat(mPhysicalDeviceAttributes2.getAttestationChallenge().toByteArray()).isEqualTo( + "player456".getBytes(UTF_8)); + assertThat(mVirtualDeviceAttributes.getAttestationChallenge().toByteArray()).isEqualTo( + "player456".getBytes(UTF_8)); + } + + @Test + public void parseCertificate_verifiedBootState() { + assertThat(mPhysicalDeviceAttributes.getVerifiedBootState()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.VerifiedBootState.UNVERIFIED); + assertThat(mPhysicalDeviceAttributes2.getVerifiedBootState()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.VerifiedBootState.VERIFIED); + assertThat(mVirtualDeviceAttributes.getVerifiedBootState()).isNull(); + } + + @Test + public void parseCertificate_keyBootPatchLevel() { + assertThat(mPhysicalDeviceAttributes.getKeyBootPatchLevel()).isEqualTo(201907); + assertThat(mPhysicalDeviceAttributes2.getKeyBootPatchLevel()).isEqualTo(20220105); + } + + @Test + public void parseCertificate_keyBootPatchLevelNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyBootPatchLevel is not set.")); + + mVirtualDeviceAttributes.getKeyBootPatchLevel(); + } + + @Test + public void parseCertificate_keyOsPatchLevel() { + assertThat(mPhysicalDeviceAttributes.getKeyOsPatchLevel()).isEqualTo(201907); + assertThat(mPhysicalDeviceAttributes2.getKeyOsPatchLevel()).isEqualTo(202201); + } + + @Test + public void parseCertificate_keyOsPatchLevelNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyOsPatchLevel is not set.")); + + mVirtualDeviceAttributes.getKeyOsPatchLevel(); + } + + @Test + public void parseCertificate_keyVendorPatchLevel() { + assertThat(mPhysicalDeviceAttributes.getKeyVendorPatchLevel()).isEqualTo(201907); + assertThat(mPhysicalDeviceAttributes2.getKeyVendorPatchLevel()).isEqualTo(20220105); + } + + @Test + public void parseCertificate_keyVendorPatchLevelNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyVendorPatchLevel is not set.")); + + mVirtualDeviceAttributes.getKeyVendorPatchLevel(); + } + + @Test + public void parseCertificate_keyAuthenticatorType() { + assertThat(mPhysicalDeviceAttributes.getKeyAuthenticatorType()).isEqualTo(0); + assertThat(mPhysicalDeviceAttributes2.getKeyAuthenticatorType()).isEqualTo(0); + } + + @Test + public void parseCertificate_keyOsVersion() { + assertThat(mPhysicalDeviceAttributes.getKeyOsVersion()).isEqualTo(0); + assertThat(mPhysicalDeviceAttributes2.getKeyOsVersion()).isEqualTo(120000); + } + + @Test + public void parseCertificate_keyOsVersionNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyOsVersion is not set.")); + + mVirtualDeviceAttributes.getKeyOsVersion(); + } + + @Test + public void parseCertificate_verifiedBootHash() { + assertThat(mPhysicalDeviceAttributes.getVerifiedBootHash()).isNotEmpty(); + assertThat(mPhysicalDeviceAttributes2.getVerifiedBootHash()).isNotEmpty(); + } + + @Test + public void parseCertificate_verifiedBootKey() { + assertThat(mPhysicalDeviceAttributes.getVerifiedBootKey()).isNotEmpty(); + assertThat(mPhysicalDeviceAttributes2.getVerifiedBootKey()).isNotEmpty(); + } + + @Test + public void parseCertificate_isVerifiedBootLocked() { + assertThat(mPhysicalDeviceAttributes.isVerifiedBootLocked()).isFalse(); + assertThat(mPhysicalDeviceAttributes2.isVerifiedBootLocked()).isTrue(); + } + + @Test + public void parseCertificate_isVerifiedBootLockedNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("VerifiedBootLocked is not set.")); + + mVirtualDeviceAttributes.isVerifiedBootLocked(); + } + + @Test + public void parseCertificate_applicationPackageNameVersion() { + assertThat(mPhysicalDeviceAttributes.getApplicationPackageNameVersion()).isNotEmpty(); + } + + @Test + public void parseCertificate_applicationCertificateDigests() { + assertThat(mPhysicalDeviceAttributes.getApplicationCertificateDigests()).isNotEmpty(); + } + + @Test + public void parseCertificate_valuesNotSet() { + assertThat(mPhysicalDeviceAttributes.getDeviceBrand()).isNull(); + assertThat(mPhysicalDeviceAttributes.getDeviceName()).isNull(); + assertThat(mPhysicalDeviceAttributes.getDeviceProductName()).isNull(); + assertThat(mPhysicalDeviceAttributes.isKeyAllowedForAllApplications()).isFalse(); + assertThat(mPhysicalDeviceAttributes2.getDeviceBrand()).isNull(); + assertThat(mPhysicalDeviceAttributes2.getDeviceName()).isNull(); + assertThat(mPhysicalDeviceAttributes2.getDeviceProductName()).isNull(); + assertThat(mPhysicalDeviceAttributes2.isKeyAllowedForAllApplications()).isFalse(); + } + + @Test + public void parseCertificate_keyRequiresUnlockedDeviceNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyRequiresUnlockedDevice is not set.")); + + mPhysicalDeviceAttributes.isKeyRequiresUnlockedDevice(); + } + + private X509Certificate generateCertificate(String certificateString) + throws Exception { + return generateCertificates(certificateString).get(0); + } + + private List<X509Certificate> generateCertificates(String certificateString) + throws Exception { + Collection<? extends Certificate> certificates = mFactory.generateCertificates( + InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() + .open(certificateString)); + + ArrayList<X509Certificate> x509Certs = new ArrayList<>(); + for (Certificate cert : certificates) { + x509Certs.add((X509Certificate) cert); + } + return x509Certs; + } +} diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt new file mode 100644 index 000000000000..45f2e5c6fdf7 --- /dev/null +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -0,0 +1,175 @@ +package com.android.server.security + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE +import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS +import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import java.io.ByteArrayOutputStream +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.TrustAnchor +import java.security.cert.X509Certificate +import java.time.LocalDate + +/** Test for Peer Device attestation verifier. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class AttestationVerificationPeerDeviceVerifierTest { + private val certificateFactory = CertificateFactory.getInstance("X.509") + @Mock private lateinit var context: Context + private lateinit var trustAnchors: HashSet<TrustAnchor> + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + val rootCerts = TEST_ROOT_CERT_FILENAME.fromPEMFileToCerts() + trustAnchors = HashSet<TrustAnchor>() + rootCerts.forEach { + trustAnchors.add(TrustAnchor(it as X509Certificate, null)) + } + } + + @Test + fun verifyAttestation_returnsSuccessTypeChallenge() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsSuccessTypePublicKey() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1)) + + val leafCert = + (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0] + as X509Certificate + val pkRequirements = Bundle() + pkRequirements.putByteArray(PARAM_PUBLIC_KEY, leafCert.publicKey.encoded) + + val result = verifier.verifyAttestation( + TYPE_PUBLIC_KEY, pkRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2023, 3, 1), + LocalDate.of(2023, 2, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureTrustedAnchorEmpty() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, HashSet(), false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureTrustedAnchorMismatch() { + val badTrustAnchorsCerts = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToCerts() + val badTrustAnchors = HashSet<TrustAnchor>() + badTrustAnchorsCerts.forEach { + badTrustAnchors.add(TrustAnchor(it as X509Certificate, null)) + } + + val verifier = AttestationVerificationPeerDeviceVerifier( + context, badTrustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + fun verifyAttestation_returnsFailureChallenge() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + private fun String.fromPEMFileToCerts(): Collection<Certificate> { + return certificateFactory.generateCertificates( + InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() + .open(this)) + } + + private fun String.fromPEMFileToByteArray(): ByteArray { + val certs = this.fromPEMFileToCerts() + val bos = ByteArrayOutputStream() + certs.forEach { + bos.write(it.encoded) + } + return bos.toByteArray() + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } + + companion object { + private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" + private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = + "test_attestation_with_root_certs.pem" + private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" + } +} diff --git a/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java b/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java index 35f1e585931b..644d450a7a88 100644 --- a/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java +++ b/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java @@ -24,6 +24,8 @@ import android.net.SntpClient; import android.os.Environment; import android.util.Log; +import libcore.io.Streams; + import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; @@ -37,8 +39,6 @@ import java.net.InetAddress; import java.net.URL; import java.util.Random; -import libcore.io.Streams; - /* * Test Service that tries to connect to the web via different methods and outputs the results to * the log and a output file. @@ -146,7 +146,7 @@ public class BandwidthEnforcementTestService extends IntentService { final ConnectivityManager mCM = context.getSystemService(ConnectivityManager.class); final Network network = mCM.getActiveNetwork(); - if (client.requestTime("0.pool.ntp.org", 10000, network)) { + if (client.requestTime("0.pool.ntp.org", SntpClient.STANDARD_NTP_PORT, 10000, network)) { return true; } return false; diff --git a/tests/BatteryStatsPerfTest/AndroidManifest.xml b/tests/BatteryStatsPerfTest/AndroidManifest.xml index 7633d5283f5e..ab5728e75b9f 100644 --- a/tests/BatteryStatsPerfTest/AndroidManifest.xml +++ b/tests/BatteryStatsPerfTest/AndroidManifest.xml @@ -20,6 +20,8 @@ <application> <uses-library android:name="android.test.runner" /> + <service android:name="com.android.internal.os.BatteryUsageStatsPerfTest$BatteryUsageStatsService" + android:process=":BatteryUsageStatsService" /> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java deleted file mode 100644 index 6266cda204b0..000000000000 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.BatteryStats; -import android.os.Bundle; -import android.os.UserHandle; -import android.perftests.utils.BenchmarkState; -import android.perftests.utils.PerfStatusReporter; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class BatteryStatsHelperPerfTest { - - @Rule - public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - - /** - * Measures the performance of {@link BatteryStatsHelper#getStats()}, which triggers - * a battery stats sync on every iteration. - */ - @Test - public void testGetStats_forceUpdate() { - final Context context = InstrumentationRegistry.getContext(); - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - state.pauseTiming(); - statsHelper.clearStats(); - state.resumeTiming(); - - statsHelper.getStats(); - - assertThat(statsHelper.getUsageList()).isNotEmpty(); - } - } - - /** - * Measures performance of the {@link BatteryStatsHelper#getStats(boolean)}, which does - * not trigger a sync and just returns current values. - */ - @Test - public void testGetStats_cached() { - final Context context = InstrumentationRegistry.getContext(); - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - state.pauseTiming(); - statsHelper.clearStats(); - state.resumeTiming(); - - statsHelper.getStats(false /* forceUpdate */); - - assertThat(statsHelper.getUsageList()).isNotEmpty(); - } - } - - @Test - public void testPowerCalculation() { - final Context context = InstrumentationRegistry.getContext(); - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.getStats(); - - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - // This will use the cached BatteryStatsObject - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - assertThat(statsHelper.getUsageList()).isNotEmpty(); - } - } - - @Test - public void testEndToEnd() { - final Context context = InstrumentationRegistry.getContext(); - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.clearStats(); - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - state.pauseTiming(); - - List<BatterySipper> usageList = statsHelper.getUsageList(); - double power = 0; - for (int i = 0; i < usageList.size(); i++) { - BatterySipper sipper = usageList.get(i); - power += sipper.sumPower(); - } - - assertThat(power).isGreaterThan(0.0); - - state.resumeTiming(); - } - } -} diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java index 54d70478f762..fe2fe0b40891 100644 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java +++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java @@ -18,13 +18,25 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import android.app.Service; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.BatteryConsumer; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Binder; +import android.os.ConditionVariable; +import android.os.IBinder; +import android.os.Parcel; import android.os.UidBatteryConsumer; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -54,7 +66,8 @@ public class BatteryUsageStatsPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - BatteryUsageStats batteryUsageStats = batteryStatsManager.getBatteryUsageStats(); + BatteryUsageStats batteryUsageStats = batteryStatsManager.getBatteryUsageStats( + new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(0).build()); state.pauseTiming(); @@ -71,4 +84,118 @@ public class BatteryUsageStatsPerfTest { state.resumeTiming(); } } + + private final ConditionVariable mServiceConnected = new ConditionVariable(); + private IBinder mService; + + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + mService = service; + mServiceConnected.open(); + } + + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + }; + + /** + * Measures the performance of transferring BatteryUsageStats over a Binder. + */ + @Test + public void testBatteryUsageStatsTransferOverBinder() throws Exception { + final Context context = InstrumentationRegistry.getContext(); + context.bindService( + new Intent(context, BatteryUsageStatsService.class), + mConnection, Context.BIND_AUTO_CREATE); + mServiceConnected.block(30000); + assertThat(mService).isNotNull(); + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + final Parcel data = Parcel.obtain(); + final Parcel reply = Parcel.obtain(); + mService.transact(42, data, reply, 0); + final BatteryUsageStats batteryUsageStats = + BatteryUsageStats.CREATOR.createFromParcel(reply); + reply.recycle(); + data.recycle(); + + state.pauseTiming(); + + assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); + assertThat(batteryUsageStats.getUidBatteryConsumers()).hasSize(1000); + final UidBatteryConsumer uidBatteryConsumer = + batteryUsageStats.getUidBatteryConsumers().get(0); + assertThat(uidBatteryConsumer.getConsumedPower(1)).isEqualTo(123); + + state.resumeTiming(); + } + + context.unbindService(mConnection); + } + + /* This service runs in a separate process */ + public static class BatteryUsageStatsService extends Service { + private final BatteryUsageStats mBatteryUsageStats; + + public BatteryUsageStatsService() { + mBatteryUsageStats = buildBatteryUsageStats(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return new Binder() { + @Override + protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, + int flags) { + mBatteryUsageStats.writeToParcel(reply, 0); + return true; + } + }; + } + } + + private static BatteryUsageStats buildBatteryUsageStats() { + final BatteryUsageStats.Builder builder = + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false) + .setBatteryCapacity(4000) + .setDischargePercentage(20) + .setDischargedPowerRange(1000, 2000) + .setStatsStartTimestamp(1000) + .setStatsEndTimestamp(3000); + + builder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .setConsumedPower(123) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 10100) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) + .setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU, 10300) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); + + for (int i = 0; i < 1000; i++) { + final UidBatteryConsumer.Builder consumerBuilder = + builder.getOrCreateUidBatteryConsumerBuilder(i) + .setPackageWithHighestDrain("example.packagename" + i) + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000); + for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; + componentId++) { + consumerBuilder.setConsumedPower(componentId, componentId * 123.0, + BatteryConsumer.POWER_MODEL_POWER_PROFILE); + consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000); + } + + consumerBuilder.setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321); + } + return builder.build(); + } } diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java index 56db4f98e160..f4692983675d 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java @@ -17,6 +17,7 @@ package com.android.utils.blob; import static com.android.utils.blob.Utils.BUFFER_SIZE_BYTES; import static com.android.utils.blob.Utils.copy; +import static com.android.utils.blob.Utils.writeRandomData; import static com.google.common.truth.Truth.assertThat; @@ -123,7 +124,7 @@ public class FakeBlobData { public void prepare() throws Exception { try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) { - writeRandomData(file, mFileSize); + writeRandomData(file, mRandom, mFileSize); } mFileDigest = FileUtils.digest(mFile, "SHA-256"); mExpiryTimeMs = System.currentTimeMillis() + mExpiryDurationMs; @@ -239,18 +240,4 @@ public class FakeBlobData { } return digest.digest(); } - - private void writeRandomData(RandomAccessFile file, long fileSize) - throws Exception { - long bytesWritten = 0; - final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; - while (bytesWritten < fileSize) { - mRandom.nextBytes(buffer); - final int toWrite = (bytesWritten + buffer.length <= fileSize) - ? buffer.length : (int) (fileSize - bytesWritten); - file.seek(bytesWritten); - file.write(buffer, 0, toWrite); - bytesWritten += toWrite; - } - } } diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java index 2d230a74a477..f6c0e6dbcf22 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java @@ -30,11 +30,14 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Random; public class Utils { public static final String TAG = "BlobStoreTest"; @@ -164,6 +167,25 @@ public class Utils { runShellCmd("cmd blob_store idle-maintenance"); } + public static void writeRandomData(File file, long fileSizeBytes) + throws Exception { + writeRandomData(new RandomAccessFile(file, "rw"), new Random(0), fileSizeBytes); + } + + public static void writeRandomData(RandomAccessFile file, Random random, long fileSizeBytes) + throws Exception { + long bytesWritten = 0; + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + while (bytesWritten < fileSizeBytes) { + random.nextBytes(buffer); + final int toWrite = (bytesWritten + buffer.length <= fileSizeBytes) + ? buffer.length : (int) (fileSizeBytes - bytesWritten); + file.seek(bytesWritten); + file.write(buffer, 0, toWrite); + bytesWritten += toWrite; + } + } + public static String runShellCmd(String cmd) throws IOException { final UiDevice uiDevice = UiDevice.getInstance( InstrumentationRegistry.getInstrumentation()); diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS index 7ee0d9a5e77e..57303e748738 100644 --- a/tests/BootImageProfileTest/OWNERS +++ b/tests/BootImageProfileTest/OWNERS @@ -1,4 +1,4 @@ calin@google.com -mathieuc@google.com ngeoffray@google.com +vmarko@google.com yawanng@google.com diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java index 4ecca2dc4c39..cf5658644a61 100644 --- a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java +++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java @@ -31,6 +31,8 @@ public class BootImageProfileTest implements IDeviceTest { private static final String SYSTEM_SERVER_PROFILE = "/data/misc/profiles/cur/0/android/primary.prof"; private static final boolean USE_PHENOTYPE = false; + private static final String DALVIK_VM_EXTRA_OPTS = + "-Xusejit:false -Xint -Xjitsaveprofilinginfo"; @Override public void setDevice(ITestDevice testDevice) { @@ -54,10 +56,10 @@ public class BootImageProfileTest implements IDeviceTest { private String setProperty(String property, String value) throws Exception { if (USE_PHENOTYPE) { return mTestDevice.executeShellCommand( - "device_config put runtime_native_boot " + property + " " + value); + String.format("device_config put runtime_native_boot %s '%s'", property, value)); } else { return mTestDevice.executeShellCommand( - "setprop dalvik.vm." + property + " " + value); + String.format("setprop dalvik.vm.%s '%s'", property, value)); } } @@ -69,6 +71,8 @@ public class BootImageProfileTest implements IDeviceTest { assertTrue("profile boot class path not enabled: " + res, "true".equals(res)); res = getProperty("profilesystemserver"); assertTrue("profile system server not enabled: " + res, "true".equals(res)); + res = getProperty("extra-opts"); + assertTrue("extra options not set: " + res, DALVIK_VM_EXTRA_OPTS.equals(res)); } private boolean forceSaveProfile(String pkg) throws Exception { @@ -91,16 +95,20 @@ public class BootImageProfileTest implements IDeviceTest { boolean profileBootClassPath = "true".equals(pbcp); String pss = getProperty("profilesystemserver"); boolean profileSystemServer = "true".equals(pss); - if (profileBootClassPath && profileSystemServer) { + String extraOpts = getProperty("extra-opts"); + boolean extraOptsOk = DALVIK_VM_EXTRA_OPTS.equals(extraOpts); + if (profileBootClassPath && profileSystemServer && extraOptsOk) { break; } if (i == numIterations) { assertTrue("profile system server not enabled: " + pss, profileSystemServer); assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath); + assertTrue("extra options not set: " + extraOpts, extraOptsOk); } setProperty("profilebootclasspath", "true"); setProperty("profilesystemserver", "true"); + setProperty("extra-opts", DALVIK_VM_EXTRA_OPTS); Thread.sleep(1000); } @@ -114,12 +122,15 @@ public class BootImageProfileTest implements IDeviceTest { boolean profileBootClassPath = "true".equals(pbcp); String pss = getProperty("profilesystemserver"); boolean profileSystemServer = "true".equals(pss); + String extraOpts = getProperty("extra-opts"); + boolean extraOptsOk = DALVIK_VM_EXTRA_OPTS.equals(extraOpts); if (profileBootClassPath && profileSystemServer) { break; } if (i == numIterations) { assertTrue("profile system server not enabled: " + pss, profileSystemServer); assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath); + assertTrue("extra options not set: " + extraOpts, extraOptsOk); } Thread.sleep(1000); } diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/.project b/tests/Camera2Tests/SmartCamera/SimpleCamera/.project deleted file mode 100644 index 2517e2d4f93d..000000000000 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/.project +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<projectDescription> - <name>CameraShoot</name> - <comment></comment> - <projects> - </projects> - <buildSpec> - <buildCommand> - <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>org.eclipse.jdt.core.javabuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.ApkBuilder</name> - <arguments> - </arguments> - </buildCommand> - </buildSpec> - <natures> - <nature>com.android.ide.eclipse.adt.AndroidNature</nature> - <nature>org.eclipse.jdt.core.javanature</nature> - </natures> -</projectDescription> diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.bp b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.bp new file mode 100644 index 000000000000..4fff969359c8 --- /dev/null +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "SmartCamera", + optimize: { + enabled: false, + }, + // comment it out for now since we need use some hidden APIs + sdk_version: "current", + static_libs: ["android-ex-camera2"], + srcs: [ + "src/**/*.java", + ], + jni_libs: ["libsmartcamera_jni"], +} diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk deleted file mode 100644 index 6003628ffb0d..000000000000 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2013 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -ifneq ($(TARGET_BUILD_JAVA_SUPPORT_LEVEL),) - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_PROGUARD_ENABLED := disabled - -# comment it out for now since we need use some hidden APIs -LOCAL_SDK_VERSION := current - -LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 - -LOCAL_SRC_FILES := \ - $(call all-java-files-under, src) \ - $(call all-renderscript-files-under, src) - -LOCAL_PACKAGE_NAME := SmartCamera -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../NOTICE -LOCAL_JNI_SHARED_LIBRARIES := libsmartcamera_jni - -include $(BUILD_PACKAGE) - -# Include packages in subdirectories -include $(call all-makefiles-under,$(LOCAL_PATH)) - -endif diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java index aa5739414df4..00fe6f2f866e 100644 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java @@ -24,10 +24,12 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.util.Log; + import androidx.media.filterfw.FrameImage2D; import androidx.media.filterfw.FrameValue; import androidx.media.filterfw.RenderTarget; +import java.io.IOException; import java.util.concurrent.LinkedBlockingQueue; @TargetApi(16) @@ -276,12 +278,13 @@ public class MediaDecoder implements } @TargetApi(17) - private void retrieveDefaultRotation() { + private void retrieveDefaultRotation() throws IOException { MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); metadataRetriever.setDataSource(mContext, mUri); String rotationString = metadataRetriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); mDefaultRotation = rotationString == null ? 0 : Integer.parseInt(rotationString); + metadataRetriever.release(); } private void onStop(boolean notifyListener) { diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.bp b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.bp new file mode 100644 index 000000000000..5edb1de9586e --- /dev/null +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.bp @@ -0,0 +1,36 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "SmartCamera-tests", + platform_apis: true, + srcs: ["src/**/*.java"], + libs: ["android.test.base"], + static_libs: [ + "guava", + "junit", + ], + optimize: { + enabled: false, + }, + instrumentation_for: "SmartCamera", +} diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk deleted file mode 100644 index c23d593d4f86..000000000000 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (C) 2013 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -# LOCAL_SDK_VERSION := current -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_PACKAGE_NAME := SmartCamera-tests -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE - -LOCAL_SRC_FILES += $(call all-java-files-under, src) - -LOCAL_JAVA_LIBRARIES := android.test.base -LOCAL_STATIC_JAVA_LIBRARIES := guava junit - -LOCAL_PROGUARD_ENABLED := disabled - -LOCAL_INSTRUMENTATION_FOR := SmartCamera - -include $(BUILD_PACKAGE) diff --git a/tests/CanvasCompare/Android.bp b/tests/CanvasCompare/Android.bp new file mode 100644 index 000000000000..98831154ddc2 --- /dev/null +++ b/tests/CanvasCompare/Android.bp @@ -0,0 +1,63 @@ +// +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "CanvasCompare", + srcs: [ + "src/**/*.java", + ":CanvasCompare-rscript{CanvasCompare.srcjar}", + ], + resource_zips: [ + ":CanvasCompare-rscript{CanvasCompare.res.zip}", + ], + platform_apis: true, + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: ["junit"], +} + +genrule { + name: "CanvasCompare-rscript", + srcs: [ + "src/**/*.rscript", + ":rs_script_api", + ":rs_clang_headers", + ], + tools: [ + "llvm-rs-cc", + "soong_zip", + ], + out: [ + "CanvasCompare.srcjar", + "CanvasCompare.res.zip", + ], + cmd: "for f in $(locations src/**/*.rscript); do " + + " $(location llvm-rs-cc) -o $(genDir)/res/raw -p $(genDir)/src " + + " -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " + + " -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " + + "done && " + + "$(location soong_zip) -srcjar -o $(location CanvasCompare.srcjar) -C $(genDir)/src -D $(genDir)/src &&" + + "$(location soong_zip) -o $(location CanvasCompare.res.zip) -C $(genDir)/res -D $(genDir)/res", +} diff --git a/tests/CanvasCompare/Android.mk b/tests/CanvasCompare/Android.mk deleted file mode 100644 index b82ae65b4356..000000000000 --- a/tests/CanvasCompare/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (C) 2012 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) - -LOCAL_PACKAGE_NAME := CanvasCompare -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_MODULE_TAGS := tests - -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base -LOCAL_STATIC_JAVA_LIBRARIES := junit - -include $(BUILD_PACKAGE) diff --git a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java index 4ec86b186285..56848b89be6f 100644 --- a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java +++ b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java @@ -15,20 +15,19 @@ */ package com.android.tests.dataidle; +import static android.net.NetworkStats.METERED_YES; + +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.content.Context; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; -import android.net.NetworkStats; -import android.net.NetworkStats.Entry; import android.net.NetworkTemplate; -import android.net.TrafficStats; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; import android.util.Log; +import java.util.Set; + /** * A test that dumps data usage to instrumentation out, used for measuring data usage for idle * devices. @@ -36,7 +35,7 @@ import android.util.Log; public class DataIdleTest extends InstrumentationTestCase { private TelephonyManager mTelephonyManager; - private INetworkStatsService mStatsService; + private NetworkStatsManager mStatsManager; private static final String LOG_TAG = "DataIdleTest"; private final static int INSTRUMENTATION_IN_PROGRESS = 2; @@ -44,8 +43,7 @@ public class DataIdleTest extends InstrumentationTestCase { protected void setUp() throws Exception { super.setUp(); Context c = getInstrumentation().getTargetContext(); - mStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + mStatsManager = c.getSystemService(NetworkStatsManager.class); mTelephonyManager = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE); } @@ -53,7 +51,9 @@ public class DataIdleTest extends InstrumentationTestCase { * Test that dumps all the data usage metrics for wifi to instrumentation out. */ public void testWifiIdle() { - NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard(); + final NetworkTemplate template = new NetworkTemplate + .Builder(NetworkTemplate.MATCH_WIFI) + .build(); fetchStats(template); } @@ -61,8 +61,11 @@ public class DataIdleTest extends InstrumentationTestCase { * Test that dumps all the data usage metrics for all mobile to instrumentation out. */ public void testMobile() { - String subscriberId = mTelephonyManager.getSubscriberId(); - NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId); + final String subscriberId = mTelephonyManager.getSubscriberId(); + NetworkTemplate template = new NetworkTemplate + .Builder(NetworkTemplate.MATCH_MOBILE) + .setMeteredness(METERED_YES) + .setSubscriberIds(Set.of(subscriberId)).build(); fetchStats(template); } @@ -72,49 +75,26 @@ public class DataIdleTest extends InstrumentationTestCase { * @param template {@link NetworkTemplate} to match. */ private void fetchStats(NetworkTemplate template) { - INetworkStatsSession session = null; try { - mStatsService.forceUpdate(); - session = mStatsService.openSession(); - final NetworkStats stats = session.getSummaryForAllUid( - template, Long.MIN_VALUE, Long.MAX_VALUE, false); - reportStats(stats); - } catch (RemoteException e) { + mStatsManager.forceUpdate(); + final NetworkStats.Bucket bucket = + mStatsManager.querySummaryForDevice(template, Long.MIN_VALUE, Long.MAX_VALUE); + reportStats(bucket); + } catch (RuntimeException e) { Log.w(LOG_TAG, "Failed to fetch network stats."); - } finally { - TrafficStats.closeQuietly(session); } } /** * Print network data usage stats to instrumentation out - * @param stats {@link NetworkorStats} to print + * @param bucket {@link NetworkStats} to print */ - void reportStats(NetworkStats stats) { + void reportStats(NetworkStats.Bucket bucket) { Bundle result = new Bundle(); - long rxBytes = 0; - long txBytes = 0; - long rxPackets = 0; - long txPackets = 0; - for (int i = 0; i < stats.size(); ++i) { - // Label will be iface_uid_tag_set - Entry statsEntry = stats.getValues(i, null); - // Debugging use. - /* - String labelTemplate = String.format("%s_%d_%d_%d", statsEntry.iface, statsEntry.uid, - statsEntry.tag, statsEntry.set) + "_%s"; - result.putLong(String.format(labelTemplate, "rxBytes"), statsEntry.rxBytes); - result.putLong(String.format(labelTemplate, "txBytes"), statsEntry.txBytes); - */ - rxPackets += statsEntry.rxPackets; - rxBytes += statsEntry.rxBytes; - txPackets += statsEntry.txPackets; - txBytes += statsEntry.txBytes; - } - result.putLong("Total rx Bytes", rxBytes); - result.putLong("Total tx Bytes", txBytes); - result.putLong("Total rx Packets", rxPackets); - result.putLong("Total tx Packets", txPackets); + result.putLong("Total rx Bytes", bucket.getRxBytes()); + result.putLong("Total tx Bytes", bucket.getTxBytes()); + result.putLong("Total rx Packets", bucket.getRxPackets()); + result.putLong("Total tx Packets", bucket.getTxPackets()); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, result); } diff --git a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java index 6985702c24e6..9a88abdd7b37 100644 --- a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java +++ b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java @@ -81,7 +81,8 @@ public class DozeTestDream extends DreamService { intent.setPackage(getPackageName()); IntentFilter filter = new IntentFilter(); filter.addAction(intent.getAction()); - registerReceiver(mAlarmReceiver, filter); + registerReceiver(mAlarmReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.bp b/tests/DynamicCodeLoggerIntegrationTests/Android.bp new file mode 100644 index 000000000000..448d46fe5e4e --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/Android.bp @@ -0,0 +1,60 @@ +// +// Copyright 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +java_test_helper_library { + name: "DynamicCodeLoggerTestLibrary", + srcs: ["src/com/android/dcl/**/*.java"], + +} + +cc_library_shared { + name: "DynamicCodeLoggerNativeTestLibrary", + srcs: ["src/cpp/com_android_dcl_Jni.cpp"], + header_libs: ["jni_headers"], + sdk_version: "28", + stl: "c++_static", +} + +cc_binary { + name: "DynamicCodeLoggerNativeExecutable", + srcs: ["src/cpp/test_executable.cpp"], +} + +android_test { + name: "DynamicCodeLoggerIntegrationTests", + + sdk_version: "current", + test_suites: ["device-tests"], + certificate: "shared", + srcs: ["src/com/android/server/pm/**/*.java"], + + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + ], + + compile_multilib: "both", + jni_libs: ["DynamicCodeLoggerNativeTestLibrary"], + + java_resources: [ + ":DynamicCodeLoggerTestLibrary", + ":DynamicCodeLoggerNativeExecutable", + ], +} diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.mk b/tests/DynamicCodeLoggerIntegrationTests/Android.mk deleted file mode 100644 index dab83046c28f..000000000000 --- a/tests/DynamicCodeLoggerIntegrationTests/Android.mk +++ /dev/null @@ -1,95 +0,0 @@ -# -# Copyright 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) - -# Build a tiny library that the test app can dynamically load - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_MODULE := DynamicCodeLoggerTestLibrary -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl) - -include $(BUILD_JAVA_LIBRARY) - -dynamiccodeloggertest_jar := $(LOCAL_BUILT_MODULE) - - -# Also build a native library that the test app can dynamically load - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_MODULE := DynamicCodeLoggerNativeTestLibrary -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp -LOCAL_HEADER_LIBRARIES := jni_headers -LOCAL_SDK_VERSION := 28 -LOCAL_NDK_STL_VARIANT := c++_static - -include $(BUILD_SHARED_LIBRARY) - -# And a standalone native executable that we can exec. - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_MODULE := DynamicCodeLoggerNativeExecutable -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SRC_FILES := src/cpp/test_executable.cpp - -include $(BUILD_EXECUTABLE) - -dynamiccodeloggertest_executable := $(LOCAL_BUILT_MODULE) - -# Build the test app itself - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_PACKAGE_NAME := DynamicCodeLoggerIntegrationTests -LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_CERTIFICATE := shared -LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) - -LOCAL_STATIC_JAVA_LIBRARIES := \ - androidx.test.rules \ - truth-prebuilt \ - -# Include both versions of the .so if we have 2 arch -LOCAL_MULTILIB := both -LOCAL_JNI_SHARED_LIBRARIES := \ - DynamicCodeLoggerNativeTestLibrary \ - -# This gets us the javalib.jar built by DynamicCodeLoggerTestLibrary above as well as the various -# native binaries. -LOCAL_JAVA_RESOURCE_FILES := \ - $(dynamiccodeloggertest_jar) \ - $(dynamiccodeloggertest_executable) \ - -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -include $(BUILD_PACKAGE) diff --git a/tests/DynamicCodeLoggerIntegrationTests/OWNERS b/tests/DynamicCodeLoggerIntegrationTests/OWNERS new file mode 100644 index 000000000000..d9eb1413cb1e --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/OWNERS @@ -0,0 +1 @@ +file:/services/core/java/com/android/server/pm/dex/OWNERS diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java index 883c172e4990..5430dee5ca31 100644 --- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java +++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java @@ -114,7 +114,8 @@ public final class DynamicCodeLoggerIntegrationTests { // Obtained via "echo -n copied.jar | sha256sum" String expectedNameHash = "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; - String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); + String expectedContentHash = copyAndHashResource( + "/DynamicCodeLoggerTestLibrary.jar", privateCopyFile); // Feed the jar to a class loader and make sure it contains what we expect. ClassLoader parentClassLoader = sContext.getClass().getClassLoader(); @@ -135,7 +136,8 @@ public final class DynamicCodeLoggerIntegrationTests { File privateCopyFile = privateFile("copied2.jar"); String expectedNameHash = "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93"; - String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); + String expectedContentHash = copyAndHashResource( + "/DynamicCodeLoggerTestLibrary.jar", privateCopyFile); // This time make sure an unknown class loader is an ancestor of the class loader we use. ClassLoader knownClassLoader = sContext.getClass().getClassLoader(); diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index 896ec9ae922c..566c725a3414 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -16,10 +16,6 @@ <!-- restart launcher to activate TAPL --> <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner"> - <!-- reboot the device to teardown any crashed tests --> - <option name="cleanup-action" value="REBOOT" /> - </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> <option name="test-file-name" value="FlickerTests.apk"/> diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS index b5561010e7f9..d40ff5659708 100644 --- a/tests/FlickerTests/OWNERS +++ b/tests/FlickerTests/OWNERS @@ -1,3 +1,4 @@ -# Bug component: 909476 +# Bug component: 1157642 include /services/core/java/com/android/server/wm/OWNERS -natanieljr@google.com
\ No newline at end of file +natanieljr@google.com +pablogamito@google.com diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 64cb790d324b..7f309e1974e1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -102,28 +102,59 @@ fun FlickerTestParameter.statusBarLayerIsVisible() { } } -fun FlickerTestParameter.navBarLayerRotatesAndScales() { +/** + * Asserts that the [FlickerComponentName.NAV_BAR] layer is at the correct position at the start + * of the SF trace + */ +fun FlickerTestParameter.navBarLayerPositionStart() { assertLayersStart { val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") + ?: throw RuntimeException("There is no display!") this.visibleRegion(FlickerComponentName.NAV_BAR) .coversExactly(WindowUtils.getNavigationBarPosition(display)) } +} + +/** + * Asserts that the [FlickerComponentName.NAV_BAR] layer is at the correct position at the end + * of the SF trace + */ +fun FlickerTestParameter.navBarLayerPositionEnd() { assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") + ?: throw RuntimeException("There is no display!") this.visibleRegion(FlickerComponentName.NAV_BAR) .coversExactly(WindowUtils.getNavigationBarPosition(display)) } } -fun FlickerTestParameter.statusBarLayerRotatesScales() { +/** + * Asserts that the [FlickerComponentName.NAV_BAR] layer is at the correct position at the start + * and end of the SF trace + */ +fun FlickerTestParameter.navBarLayerRotatesAndScales() { + navBarLayerPositionStart() + navBarLayerPositionEnd() +} + +/** + * Asserts that the [FlickerComponentName.STATUS_BAR] layer is at the correct position at the start + * of the SF trace + */ +fun FlickerTestParameter.statusBarLayerPositionStart() { assertLayersStart { val display = this.entry.displays.minByOrNull { it.id } ?: throw RuntimeException("There is no display!") this.visibleRegion(FlickerComponentName.STATUS_BAR) .coversExactly(WindowUtils.getStatusBarPosition(display)) } +} + +/** + * Asserts that the [FlickerComponentName.STATUS_BAR] layer is at the correct position at the end + * of the SF trace + */ +fun FlickerTestParameter.statusBarLayerPositionEnd() { assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } ?: throw RuntimeException("There is no display!") @@ -133,6 +164,15 @@ fun FlickerTestParameter.statusBarLayerRotatesScales() { } /** + * Asserts that the [FlickerComponentName.STATUS_BAR] layer is at the correct position at the start + * and end of the SF trace + */ +fun FlickerTestParameter.statusBarLayerRotatesScales() { + statusBarLayerPositionStart() + statusBarLayerPositionEnd() +} + +/** * Asserts that: * [originalLayer] is visible at the start of the trace * [originalLayer] becomes invisible during the trace and (in the same entry) [newLayer] @@ -141,20 +181,36 @@ fun FlickerTestParameter.statusBarLayerRotatesScales() { * * @param originalLayer Layer that should be visible at the start * @param newLayer Layer that should be visible at the end + * @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored + * when checking the transition. If true we will not fail the assertion if a rotation layer is + * visible to fill the gap between the [originalLayer] being visible and the [newLayer] being + * visible. * @param ignoreSnapshot If the snapshot layer should be ignored during the transition * (useful mostly for app launch) + * @param ignoreSplashscreen If the splashscreen layer should be ignored during the transition. + * If true then we will allow for a splashscreen to be shown before the layer is shown, + * otherwise we won't and the layer must appear immediately. */ fun FlickerTestParameter.replacesLayer( originalLayer: FlickerComponentName, newLayer: FlickerComponentName, - ignoreSnapshot: Boolean = false + ignoreEntriesWithRotationLayer: Boolean = false, + ignoreSnapshot: Boolean = false, + ignoreSplashscreen: Boolean = true ) { assertLayers { val assertion = this.isVisible(originalLayer) + + if (ignoreEntriesWithRotationLayer) { + assertion.then().isVisible(FlickerComponentName.ROTATION, isOptional = true) + } if (ignoreSnapshot) { - assertion.then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + assertion.then().isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + } + if (ignoreSplashscreen) { + assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true) } + assertion.then().isVisible(newLayer) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 9f26c31a6d63..20a2e2228d7a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -24,6 +24,9 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -65,9 +68,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) transitions { device.pressBack() wmHelper.waitForHomeActivityVisible() @@ -79,6 +82,28 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + @FlakyTest(bugId = 214452854) + @Test + fun statusBarLayerRotatesScales_shellTransit() { + assumeTrue(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 229762973) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. @@ -90,7 +115,7 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 795766fccfbd..d00fd7d7f09b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.close +import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -64,9 +65,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) transitions { device.pressHome() wmHelper.waitForHomeActivityVisible() @@ -78,6 +79,33 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 227430489) + @Test + override fun statusBarLayerRotatesScales() { + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun launcherLayerReplacesApp() { + super.launcherLayerReplacesApp() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun entireScreenCovered() { + super.entireScreenCovered() + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 229762973) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. @@ -89,7 +117,7 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index 511fc26fdb38..aaa2db768792 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -30,7 +30,6 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible @@ -47,16 +46,16 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) /** * Specification of the test transition to execute */ - protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + protected open val transition: FlickerBuilder.() -> Unit = { setup { eachRun { testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { test { - testApp.exit() + testApp.exit(wmHelper) } } } @@ -68,7 +67,7 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - transition(testSpec.config) + transition() } } @@ -189,4 +188,4 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) open fun launcherLayerReplacesApp() { testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS new file mode 100644 index 000000000000..f7c0a87f5dac --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS @@ -0,0 +1,2 @@ +# window manager > animations/transitions +# Bug component: 316275 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt new file mode 100644 index 000000000000..2dbf304a0f23 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt @@ -0,0 +1,34 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + + class FixedOrientationAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy + ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index 0b1748a6bda4..aacc17a49a24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -16,13 +16,21 @@ package com.android.server.wm.flicker.helpers +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars + import android.app.Instrumentation +import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import java.util.regex.Pattern + class ImeAppAutoFocusHelper @JvmOverloads constructor( instr: Instrumentation, private val rotation: Int, @@ -39,6 +47,16 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( waitIMEShown(device, wmHelper) } + override fun launchViaIntent( + wmHelper: WindowManagerStateHelper, + expectedWindowName: String, + action: String?, + stringExtras: Map<String, String> + ) { + super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) + waitIMEShown(uiDevice, wmHelper) + } + override fun open() { val expectedPackage = if (rotation.isRotated()) { imePackageName @@ -47,4 +65,52 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( } launcherStrategy.launch(appName, expectedPackage) } + + fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { + val button = uiDevice.wait(Until.findObject(By.res(getPackage(), + "start_dialog_themed_activity_btn")), FIND_TIMEOUT) + + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. Screen turned off)" + } + button.click() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp( + ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) + mInstrumentation.waitForIdleSync() + } + fun dismissDialog(wmHelper: WindowManagerStateHelper) { + val dialog = uiDevice.wait( + Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) + + // Pressing back key to dismiss the dialog + if (dialog != null) { + uiDevice.pressBack() + wmHelper.waitForAppTransitionIdle() + } + } + fun getInsetsVisibleFromDialog(type: Int): Boolean { + var insetsVisibilityTextView = uiDevice.wait( + Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) + if (insetsVisibilityTextView != null) { + var visibility = insetsVisibilityTextView.text.toString() + val matcher = when (type) { + ime() -> { + Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + statusBars() -> { + Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + navigationBars() -> { + Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + else -> null + } + if (matcher != null && matcher.find()) { + return matcher.group(1).equals("VISIBLE") + } + } + return false + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index 7ee6451b2797..5bd365c7eefd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -64,7 +64,6 @@ open class ImeAppHelper @JvmOverloads constructor( device.waitForIdle() } else { wmHelper.waitImeShown() - wmHelper.waitForAppTransitionIdle() } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt new file mode 100644 index 000000000000..172c4330c3c6 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt @@ -0,0 +1,60 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class ImeEditorPopupDialogAppHelper @JvmOverloads constructor( + instr: Instrumentation, + private val rotation: Int, + private val imePackageName: String = IME_PACKAGE, + launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent() +) : ImeAppHelper(instr, launcherName, component) { + override fun openIME( + device: UiDevice, + wmHelper: WindowManagerStateHelper? + ) { + val editText = device.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT) + + require(editText != null) { + "Text field not found, this usually happens when the device " + + "was left in an unknown state (e.g. in split screen)" + } + editText.click() + waitIMEShown(device, wmHelper) + } + + fun dismissDialog(wmHelper: WindowManagerStateHelper) { + val dismissButton = uiDevice.wait( + Until.findObject(By.text("Dismiss")), FIND_TIMEOUT) + + // Pressing back key to dismiss the dialog + if (dismissButton != null) { + dismissButton.click() + wmHelper.waitForAppTransitionIdle() + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt new file mode 100644 index 000000000000..16c4c254f9e3 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt @@ -0,0 +1,34 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + +class ImeStateInitializeHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt new file mode 100644 index 000000000000..4e360f98723e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt @@ -0,0 +1,53 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class NotificationAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + fun postNotification(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val button = device.wait( + Until.findObject(By.res(getPackage(), "post_notification")), + FIND_TIMEOUT) + + require(button != null) { + "Post notification button not found, this usually happens when the device " + + "was left in an unknown state (e.g. in split screen)" + } + button.click() + + device.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT) + ?: error("Flicker Notification not found") + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt new file mode 100644 index 000000000000..bd2e5756b4a9 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt @@ -0,0 +1,34 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + +class ShowWhenLockedAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index 59e8dc826007..a135e0af067b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -19,11 +19,13 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory +import android.view.Display import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.WindowManagerConditionsFactory import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper @@ -36,17 +38,29 @@ class TwoActivitiesAppHelper @JvmOverloads constructor( .getInstance(instr) .launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + private val secondActivityComponent = + ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) { - val button = device.wait( - Until.findObject(By.res(getPackage(), "launch_second_activity")), - FIND_TIMEOUT) + val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY) + val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT) require(button != null) { "Button not found, this usually happens when the device " + "was left in an unknown state (e.g. in split screen)" } button.click() - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForFullScreenApp(component) + + device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT) + wmHelper.waitFor( + WindowManagerStateHelper.isAppFullScreen(secondActivityComponent), + WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerConditionsFactory.hasLayersAnimating().negate() + ) + } + + companion object { + private const val LAUNCH_SECOND_ACTIVITY = "launch_second_activity" } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index 5e21aff94769..dd5f33fb8669 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -33,7 +34,6 @@ import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible @@ -63,7 +63,7 @@ import org.junit.runners.Parameterized @Group2 class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -153,7 +153,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -170,7 +170,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5, + .getConfigNonRotationTests(repetitions = 3, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 0582685f2c54..5606965a6d40 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -34,7 +34,6 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible @@ -64,7 +63,7 @@ import org.junit.runners.Parameterized @Group2 class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -104,7 +103,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete } } - @FlakyTest(bugId = 190189685) + @Presubmit @Test fun imeAppWindowBecomesInvisible() { testSpec.assertWm { @@ -152,7 +151,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -179,7 +178,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 1, + .getConfigNonRotationTests(repetitions = 3, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt new file mode 100644 index 000000000000..6257484be9bd --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt @@ -0,0 +1,136 @@ +/* + * 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.server.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.flicker.traces.region.RegionSubject +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + imeTestApp.launchViaIntent(wmHelper) + imeTestApp.openIME(device, wmHelper) + } + } + transitions { + imeTestApp.dismissDialog(wmHelper) + instrumentation.uiAutomation.syncInputTransactions() + } + teardown { + eachRun { + device.pressHome() + wmHelper.waitForHomeActivityVisible() + imeTestApp.exit() + } + } + } + } + + @Postsubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Postsubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + @Postsubmit + @Test + fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible() + + @Postsubmit + @Test + fun imeLayerAndImeSnapshotVisibleOnScreen() { + testSpec.assertLayers { + this.isVisible(FlickerComponentName.IME) + .then() + .isVisible(FlickerComponentName.IME_SNAPSHOT) + .then() + .isInvisible(FlickerComponentName.IME_SNAPSHOT) + .isInvisible(FlickerComponentName.IME) + } + } + + @Postsubmit + @Test + fun imeSnapshotAssociatedOnAppVisibleRegion() { + testSpec.assertLayers { + this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") { + val imeSnapshotLayers = it.subjects.filter { + subject -> subject.name.contains( + FlickerComponentName.IME_SNAPSHOT.toLayerName()) && subject.isVisible + } + if (imeSnapshotLayers.isNotEmpty()) { + val visibleAreas = imeSnapshotLayers.mapNotNull { imeSnapshotLayer -> + imeSnapshotLayer.layer?.visibleRegion }.toTypedArray() + val imeVisibleRegion = RegionSubject.assertThat(visibleAreas, this, timestamp) + val appVisibleRegion = it.visibleRegion(imeTestApp.component) + if (imeVisibleRegion.region.isNotEmpty) { + imeVisibleRegion.coversAtMost(appVisibleRegion.region) + } + } + } + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 2, + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ), + supportedRotations = listOf(Surface.ROTATION_0) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 91b3d3dae3cd..e7a1c50821b7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -35,7 +35,8 @@ import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -119,18 +120,18 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) + assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) testSpec.navBarLayerRotatesAndScales() } @FlakyTest @Test fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) + assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) testSpec.navBarLayerRotatesAndScales() } - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -159,7 +160,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index b589969dee14..b454f0155b3e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -104,7 +104,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - @FlakyTest + @Presubmit @Test fun imeAppWindowBecomesInvisible() { testSpec.assertWm { @@ -144,7 +144,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -164,7 +164,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt new file mode 100644 index 000000000000..2f8f9441a7b9 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt @@ -0,0 +1,132 @@ +/* + * 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.server.wm.flicker.ime + +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue + +/** + * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. + * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + testApp.launchViaIntent(wmHelper) + wmHelper.waitImeShown() + testApp.startDialogThemedActivity(wmHelper) + // Verify IME insets isn't visible on dialog since it's non-IME focusable window + assertFalse(testApp.getInsetsVisibleFromDialog(ime())) + assertTrue(testApp.getInsetsVisibleFromDialog(statusBars())) + assertTrue(testApp.getInsetsVisibleFromDialog(navigationBars())) + } + } + teardown { + eachRun { + testApp.exit() + } + } + transitions { + testApp.dismissDialog(wmHelper) + } + } + } + + /** + * Checks that [FlickerComponentName.IME] layer becomes visible during the transition + */ + @FlakyTest(bugId = 215884488) + @Test + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible() + + /** + * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition + */ + @FlakyTest(bugId = 227142436) + @Test + fun imeLayerExistsEnd() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.IME) + } + } + + /** + * Checks that [FlickerComponentName.IME_SNAPSHOT] layer is invisible always. + */ + @Presubmit + @Test + fun imeSnapshotNotVisible() { + testSpec.assertLayers { + this.isInvisible(FlickerComponentName.IME_SNAPSHOT) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt index a9568b325af2..b897ca2a9c15 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -28,8 +28,8 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test @@ -71,18 +71,21 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + private val initializeApp = ImeStateInitializeHelper(instrumentation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { eachRun { - this.setRotation(testSpec.config.startRotation) + initializeApp.launchViaIntent() + this.setRotation(testSpec.startRotation) } } teardown { eachRun { + initializeApp.exit() testApp.exit() } } @@ -141,7 +144,7 @@ class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS new file mode 100644 index 000000000000..301fafa5309e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS @@ -0,0 +1,2 @@ +# ime +# Bug component: 34867 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt new file mode 100644 index 000000000000..78aea1f1fb17 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt @@ -0,0 +1,122 @@ +/* + * 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.server.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group2 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.flicker.statusBarWindowIsVisible +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window layer will become visible when switching from the fixed orientation activity. + * To run this test: `atest FlickerTests:OpenImeWindowFromFixedOrientationAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group2 +class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val fixedOrientationApp = FixedOrientationAppHelper(instrumentation) + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + fixedOrientationApp.launchViaIntent(wmHelper) + this.setRotation(Surface.ROTATION_90) + } + } + transitions { + imeTestApp.launchViaIntent(wmHelper) + } + teardown { + test { + fixedOrientationApp.exit(wmHelper) + } + } + } + } + + @Presubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Presubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + @Presubmit + @Test + fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() + + @Presubmit + @Test + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + + @FlakyTest(bugId = 206753786) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + + @Presubmit + @Test + fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index 7bf0186cd857..8fcb4b7c03f1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -126,7 +126,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -138,7 +138,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @FlakyTest + @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { @@ -152,7 +152,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt new file mode 100644 index 000000000000..0454ca0a607e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt @@ -0,0 +1,238 @@ +/* + * 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.server.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import org.junit.Assume.assumeTrue +import org.junit.Assume.assumeFalse +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window layer will be associated with the app task when going to the overview screen. + * To run this test: `atest FlickerTests:OpenImeWindowToOverViewTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + private val statusBarInvisible = WindowManagerConditionsFactory.isStatusBarVisible().negate() + private val navBarInvisible = WindowManagerConditionsFactory.isNavBarVisible().negate() + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + imeTestApp.launchViaIntent(wmHelper) + } + } + transitions { + device.pressRecentApps() + waitForRecentsActivityVisible(wmHelper) + waitNavStatusBarVisibility(wmHelper) + } + teardown { + test { + device.pressHome() + imeTestApp.exit(wmHelper) + } + } + } + } + + /** + * The bars (including status bar and navigation bar) are expected to be hidden while + * entering overview in landscape if launcher is set to portrait only. Because + * "showing portrait overview (launcher) in landscape display" is an intermediate state + * depending on the touch-up to decide the intention of gesture, the display may keep in + * landscape if return to app, or change to portrait if the gesture is to swipe-to-home. + * + * So instead of showing landscape bars with portrait launcher at the same time + * (especially return-to-home that launcher workspace becomes visible), hide the bars until + * leave overview to have cleaner appearance. + * + * b/227189877 + */ + private fun waitNavStatusBarVisibility(wmHelper: WindowManagerStateHelper) { + when { + testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isGesturalNavigation -> + wmHelper.waitFor(statusBarInvisible) + testSpec.isLandscapeOrSeascapeAtStart -> + wmHelper.waitFor(statusBarInvisible, navBarInvisible) + } + } + + @Presubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Presubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + @Presubmit + @Test + fun imeWindowIsAlwaysVisible() { + testSpec.imeWindowIsAlwaysVisible() + } + + @Presubmit + @Test + fun navBarLayerIsVisible3Button() { + assumeFalse(testSpec.isGesturalNavigation) + testSpec.navBarLayerIsVisible() + } + + /** + * Bars are expected to be hidden while entering overview in landscape (b/227189877) + */ + @Presubmit + @Test + fun navBarLayerIsVisibleInPortraitGestural() { + assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) + assumeTrue(testSpec.isGesturalNavigation) + testSpec.navBarLayerIsVisible() + } + + /** + * In the legacy transitions, the nav bar is not marked as invisible. + * In the new transitions this is fixed and the nav bar shows as invisible + */ + @Postsubmit + @Test + fun navBarLayerIsInvisibleInLandscapeGestural() { + assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) + assumeTrue(testSpec.isGesturalNavigation) + assumeTrue(isShellTransitionsEnabled) + testSpec.assertLayersStart { + this.isVisible(FlickerComponentName.NAV_BAR) + } + testSpec.assertLayersEnd { + this.isInvisible(FlickerComponentName.NAV_BAR) + } + } + + @Postsubmit + @Test + fun statusBarLayerIsVisibleInPortrait() { + assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) + testSpec.statusBarLayerIsVisible() + } + + @Presubmit + @Test + fun statusBarLayerIsInvisibleInLandscape() { + assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) + testSpec.assertLayersStart { + this.isVisible(FlickerComponentName.STATUS_BAR) + } + testSpec.assertLayersEnd { + this.isInvisible(FlickerComponentName.STATUS_BAR) + } + } + + @FlakyTest(bugId = 228011606) + @Test + fun imeLayerIsVisibleAndAssociatedWithAppWidow() { + testSpec.assertLayersStart { + isVisible(FlickerComponentName.IME).visibleRegion(FlickerComponentName.IME) + .coversAtMost(isVisible(imeTestApp.component) + .visibleRegion(imeTestApp.component).region) + } + testSpec.assertLayers { + this.invoke("imeLayerIsVisibleAndAlignAppWidow") { + val imeVisibleRegion = it.visibleRegion(FlickerComponentName.IME) + val appVisibleRegion = it.visibleRegion(imeTestApp.component) + if (imeVisibleRegion.region.isNotEmpty) { + it.isVisible(FlickerComponentName.IME) + imeVisibleRegion.coversAtMost(appVisibleRegion.region) + } + } + } + } + + private fun waitForRecentsActivityVisible( + wmHelper: WindowManagerStateHelper + ) { + val waitMsg = "state of Recents activity to be visible" + require( + wmHelper.waitFor(waitMsg) { + it.wmState.homeActivity?.let { act -> + it.wmState.isActivityVisible(act.name) + } == true || + it.wmState.recentsActivity?.let { act -> + it.wmState.isActivityVisible(act.name) + } == true + } + ) { "Recents activity should be visible" } + wmHelper.waitForAppTransitionIdle() + // Ensure WindowManagerService wait until all animations have completed + instrumentation.uiAutomation.syncInputTransactions() + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 1, + supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index f6febe9e2234..e7a33543a86b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.os.SystemProperties import android.platform.test.annotations.Presubmit +import android.view.Display import android.view.Surface -import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -37,12 +37,16 @@ import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.ConditionList import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume +import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -58,11 +62,20 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group2 -class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { +open class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) - private val isShellTransitionsEnabled = - SystemProperties.getBoolean("persist.debug.shell_transit", false) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + private val waitConditionSetup = ConditionList(listOf( + WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isHomeActivityVisible() + )) + + @Before + open fun before() { + assumeFalse(isShellTransitionsEnabled) + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -74,9 +87,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } eachRun { device.pressRecentApps() - wmHelper.waitImeGone() - wmHelper.waitForAppTransitionIdle() - this.setRotation(testSpec.config.startRotation) + wmHelper.waitFor(waitConditionSetup) + this.setRotation(testSpec.startRotation) } } transitions { @@ -129,7 +141,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun imeAppWindowVisibilityLegacy() { - Assume.assumeFalse(isShellTransitionsEnabled) + assumeFalse(isShellTransitionsEnabled) // the app starts visible in live tile, and stays visible for the duration of entering // and exiting overview. However, legacy transitions seem to have a bug which causes // everything to restart during the test, so expect the app to disappear and come back. @@ -144,10 +156,10 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Presubmit + @FlakyTest(bugId = 204570898) @Test fun imeAppWindowVisibility() { - Assume.assumeTrue(isShellTransitionsEnabled) + assumeTrue(isShellTransitionsEnabled) // the app starts visible in live tile, and stays visible for the duration of entering // and exiting overview. Since we log 1x per frame, sometimes the activity visibility // and the app visibility are updated together, sometimes not, thus ignore activity @@ -173,7 +185,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun imeLayerIsBecomesVisibleLegacy() { - Assume.assumeFalse(isShellTransitionsEnabled) + assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(FlickerComponentName.IME) .then() @@ -183,10 +195,10 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Presubmit + @FlakyTest(bugId = 204570898) @Test fun imeLayerIsBecomesVisible() { - Assume.assumeTrue(isShellTransitionsEnabled) + assumeTrue(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(FlickerComponentName.IME) } @@ -208,7 +220,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -232,11 +244,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 1, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest_ShellTransit.kt new file mode 100644 index 000000000000..7ffa51320487 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest_ShellTransit.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.ime + +import android.platform.test.annotations.Postsubmit +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group2 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window opening transitions. + * To run this test: `atest FlickerTests:ReOpenImeWindowTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group2 +@FlakyTest(bugId = 221854428) +class ReOpenImeWindowTest_ShellTransit(private val testSpec: FlickerTestParameter) + : ReOpenImeWindowTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt index 4c506b0fea4d..4b268a871fa0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.view.Display import android.view.Surface import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice @@ -31,11 +32,15 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test @@ -53,24 +58,39 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 @Presubmit -class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) { +open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = SimpleAppHelper(instrumentation) - private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { eachRun { - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - wmHelper.waitForAppTransitionIdle() + val testAppVisible = wmHelper.waitFor( + WindowManagerStateHelper.isAppFullScreen(testApp.component), + WindowManagerConditionsFactory.isAppTransitionIdle( + Display.DEFAULT_DISPLAY)) + require(testAppVisible) { + "Expected ${testApp.component.toWindowName()} to be visible" + } imeTestApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - wmHelper.waitForAppTransitionIdle() + val imeAppVisible = wmHelper.waitFor( + WindowManagerStateHelper.isAppFullScreen(imeTestApp.component), + WindowManagerConditionsFactory.isAppTransitionIdle( + Display.DEFAULT_DISPLAY)) + require(imeAppVisible) { + "Expected ${imeTestApp.component.toWindowName()} to be visible" + } imeTestApp.openIME(device, wmHelper) } @@ -86,9 +106,9 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame transitions { // [Step1]: Swipe right from imeTestApp to testApp task createTag(TAG_IME_VISIBLE) - val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - device.swipe(0, displayBounds.bounds.height(), - displayBounds.bounds.width(), displayBounds.bounds.height(), 50) + val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) + device.swipe(0, displayBounds.bounds.height, + displayBounds.bounds.width, displayBounds.bounds.height, 50) wmHelper.waitForFullScreenApp(testApp.component) wmHelper.waitForAppTransitionIdle() @@ -96,9 +116,9 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame } transitions { // [Step2]: Swipe left to back to imeTestApp task - val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(), - 0, displayBounds.bounds.height(), 50) + val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) + device.swipe(displayBounds.bounds.width, displayBounds.bounds.height, + 0, displayBounds.bounds.height, 50) wmHelper.waitForFullScreenApp(imeTestApp.component) } } @@ -109,8 +129,12 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame testSpec.assertWm { isAppWindowVisible(imeTestApp.component) .then() + .isAppSnapshotStartingWindowVisibleFor(testApp.component, isOptional = true) + .then() .isAppWindowVisible(testApp.component) .then() + .isAppSnapshotStartingWindowVisibleFor(imeTestApp.component, isOptional = true) + .then() .isAppWindowVisible(imeTestApp.component) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt new file mode 100644 index 000000000000..edd52b76cd5c --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt @@ -0,0 +1,49 @@ +/* + * 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.server.wm.flicker.ime + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before + +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME windows switching with 2-Buttons or gestural navigation. + * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 228012334) +class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter) + : SwitchImeWindowsFromGestureNavTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index f74a7718461f..cc808a0ce871 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.view.Display import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.entireScreenCovered @@ -26,12 +27,13 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.WindowManagerConditionsFactory import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -60,7 +62,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { - val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation) /** @@ -70,24 +72,24 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { - eachRun { + test { testApp.launchViaIntent(wmHelper) wmHelper.waitForFullScreenApp(testApp.component) } } teardown { test { - testApp.exit() + testApp.exit(wmHelper) } } transitions { testApp.openSecondActivity(device, wmHelper) device.pressBack() - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForFullScreenApp(testApp.component) + val firstActivityVisible = wmHelper.waitFor( + WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerStateHelper.isAppFullScreen(testApp.component)) + require(firstActivityVisible) { "Expected ${testApp.component} to be visible" } } } } @@ -155,7 +157,7 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS new file mode 100644 index 000000000000..2c414a27cacb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS @@ -0,0 +1,4 @@ +# System UI > ... > Overview (recent apps) > UI +# Bug template url: https://b.corp.google.com/issues/new?component=807991&template=1390280 = per-file *Overview* +# window manager > animations/transitions +# Bug template url: https://b.corp.google.com/issues/new?component=316275&template=1018192 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index be919cd67c1e..2f6b8f008119 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -16,17 +16,15 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice +import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import org.junit.FixMethodOrder import org.junit.Test @@ -56,17 +54,18 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { +open class OpenAppColdTest(testSpec: FlickerTestParameter) + : OpenAppFromLauncherTransition(testSpec) { /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { eachRun { removeAllTasksButHome() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -81,6 +80,11 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() { @@ -88,12 +92,12 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } /** {@inheritDoc} */ - @Postsubmit + @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() /** {@inheritDoc} */ - @Postsubmit + @Presubmit @Test override fun appWindowReplacesLauncherAsTopWindow() = super.appWindowReplacesLauncherAsTopWindow() @@ -101,17 +105,17 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** {@inheritDoc} */ @Presubmit @Test - override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible() + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() /** {@inheritDoc} */ @Presubmit @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() /** {@inheritDoc} */ @Presubmit @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + override fun entireScreenCovered() = super.entireScreenCovered() companion object { /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt new file mode 100644 index 000000000000..c6e92adce8c7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt @@ -0,0 +1,71 @@ +/* + * 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.server.wm.flicker.launch + +import android.platform.test.annotations.Presubmit +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.replacesLayer +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Test + +/** + * Base class for app launch tests + */ +abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) + : OpenAppTransition(testSpec) { + + /** + * Checks that the focus changes from the launcher to [testApp] + */ + @Presubmit + @Test + open fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges("NexusLauncherActivity", testApp.`package`) + } + } + + /** + * Checks that [LAUNCHER_COMPONENT] layer is visible at the start of the transition, and + * is replaced by [testApp], which remains visible until the end + */ + open fun appLayerReplacesLauncher() { + testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component, + ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true, + ignoreSplashscreen = true) + } + + /** + * Checks that [LAUNCHER_COMPONENT] window is visible at the start of the transition, and + * is replaced by a snapshot or splash screen (optional), and finally, is replaced by + * [testApp], which remains visible until the end + */ + @Presubmit + @Test + open fun appWindowReplacesLauncherAsTopWindow() { + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCHER_COMPONENT) + .then() + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(testApp.component) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt new file mode 100644 index 000000000000..a8cbc5dc922c --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt @@ -0,0 +1,102 @@ +/* + * 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.server.wm.flicker.launch + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from a notification from the lock screen. + * + * This test assumes the device doesn't have AOD enabled + * + * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@Postsubmit +open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) + : OpenAppFromNotificationCold(testSpec) { + + override val openingNotificationsFromLockScreen = true + + override val transition: FlickerBuilder.() -> Unit + get() = { + // Needs to run at start of transition, + // so before the transition defined in super.transition + transitions { + device.wakeUp() + } + + super.transition(this) + + // Needs to run at the end of the setup, so after the setup defined in super.transition + setup { + eachRun { + device.sleep() + wmHelper.waitFor("noAppWindowsOnTop") { + it.wmState.topVisibleAppWindow.isEmpty() + } + } + } + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 229735718) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 203538234) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 203538234) + @Test + override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 3) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt new file mode 100644 index 000000000000..cd8dea012db5 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt @@ -0,0 +1,128 @@ +/* + * 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.server.wm.flicker.launch + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test warm launching an app from a notification from the lock screen. + * + * This test assumes the device doesn't have AOD enabled + * + * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@Postsubmit +open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) + : OpenAppFromNotificationWarm(testSpec) { + + override val openingNotificationsFromLockScreen = true + + override val transition: FlickerBuilder.() -> Unit + get() = { + // Needs to run at start of transition, + // so before the transition defined in super.transition + transitions { + device.wakeUp() + } + + super.transition(this) + + // Needs to run at the end of the setup, so after the setup defined in super.transition + setup { + eachRun { + device.sleep() + wmHelper.waitFor("noAppWindowsOnTop") { + it.wmState.topVisibleAppWindow.isEmpty() + } + } + } + } + + /** + * Checks that we start of with no top windows and then [testApp] becomes the first and + * only top window of the transition, with snapshot or splash screen windows optionally showing + * first. + */ + @Test + @Postsubmit + open fun appWindowBecomesFirstAndOnlyTopWindow() { + testSpec.assertWm { + this.hasNoVisibleAppWindow() + .then() + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(testApp.component) + } + } + + /** + * Checks that the screen is locked. + */ + @Test + @Postsubmit + fun screenLockedStart() { + testSpec.assertLayersStart { + isEmpty() + } + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 229735718) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 203538234) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 3) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt new file mode 100644 index 000000000000..bc637f8f9a63 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt @@ -0,0 +1,126 @@ +/* + * 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.server.wm.flicker.launch + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from a notification from the lock screen when there is an app + * overlaid on the lock screen. + * + * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@Postsubmit +class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) + : OpenAppFromLockNotificationCold(testSpec) { + private val showWhenLockedApp: ShowWhenLockedAppHelper = + ShowWhenLockedAppHelper(instrumentation) + + // Although we are technically still locked here, the overlay app means we should open the + // notification shade as if we were unlocked. + override val openingNotificationsFromLockScreen = false + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + + setup { + eachRun { + device.wakeUpAndGoToHomeScreen() + + // Launch an activity that is shown when the device is locked + showWhenLockedApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(showWhenLockedApp.component) + + device.sleep() + wmHelper.waitFor("noAppWindowsOnTop") { + it.wmState.topVisibleAppWindow.isEmpty() + } + } + } + + teardown { + test { + showWhenLockedApp.exit(wmHelper) + } + } + } + + @Test + @Postsubmit + fun showWhenLockedAppWindowBecomesVisible() { + testSpec.assertWm { + this.hasNoVisibleAppWindow() + .then() + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(showWhenLockedApp.component) + } + } + + @Test + @Postsubmit + fun showWhenLockedAppLayerBecomesVisible() { + testSpec.assertLayers { + this.isInvisible(showWhenLockedApp.component) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(showWhenLockedApp.component) + } + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 229735718) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 3) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt new file mode 100644 index 000000000000..fe80162b5b81 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.launch + +import android.platform.test.annotations.Presubmit +import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.navBarLayerPositionEnd +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Test + +/** + * Base class for app launch tests from lock screen + */ +abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) + : OpenAppTransition(testSpec) { + + /** + * Defines the transition used to run the test + */ + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + eachRun { + device.sleep() + wmHelper.waitFor("noAppWindowsOnTop") { + it.wmState.topVisibleAppWindow.isEmpty() + } + } + } + teardown { + eachRun { + testApp.exit(wmHelper) + } + } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + } + } + + /** + * Check that we go from no focus to focus on the [testApp] + */ + @Presubmit + @Test + open fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges("", testApp.`package`) + } + } + + /** + * Checks that we start of with no top windows and then [testApp] becomes the first and only top + * window of the transition, with snapshot or splash screen windows optionally showing first. + */ + @FlakyTest(bugId = 203538234) + @Test + open fun appWindowBecomesFirstAndOnlyTopWindow() { + testSpec.assertWm { + this.hasNoVisibleAppWindow() + .then() + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(testApp.component) + } + } + + /** + * Checks that the screen is locked at the start of the transition ([colorFadComponent]) + * layer is visible + */ + @Presubmit + @Test + fun screenLockedStart() { + testSpec.assertLayersStart { + isEmpty() + } + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 203538234) + @Test + override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() + + /** + * Checks the position of the navigation bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Presubmit + @Test + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() + + /** + * Checks that the status bar layer is visible at the end of the trace + * + * It is not possible to check at the start because the screen is off + */ + @Presubmit + @Test + override fun statusBarLayerIsVisible() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.STATUS_BAR) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt new file mode 100644 index 000000000000..5022dd8f9bff --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt @@ -0,0 +1,84 @@ +/* + * 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.server.wm.flicker.launch + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from a notification. + * + * This test assumes the device doesn't have AOD enabled + * + * To run this test: `atest FlickerTests:OpenAppFromNotificationCold` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@Postsubmit +open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) + : OpenAppFromNotificationWarm(testSpec) { + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + + setup { + eachRun { + // Close the app that posted the notification to trigger a cold start next time + // it is open - can't just kill it because that would remove the notification. + taplInstrumentation.goHome() + taplInstrumentation.workspace.switchToOverview() + taplInstrumentation.overview.dismissAllTasks() + } + } + } + + @Test + @Postsubmit + override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart() + + @Test + @Postsubmit + override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 3) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt new file mode 100644 index 000000000000..812f6859c7b0 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt @@ -0,0 +1,197 @@ +/* + * 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.server.wm.flicker.launch + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.view.WindowInsets +import android.view.WindowManager +import androidx.test.filters.FlakyTest +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import org.junit.Assume +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from a notification. + * + * This test assumes the device doesn't have AOD enabled + * + * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@Postsubmit +open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) + : OpenAppTransition(testSpec) { + protected val taplInstrumentation = LauncherInstrumentation() + + override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) + + open val openingNotificationsFromLockScreen = false + + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { + test { + device.wakeUpAndGoToHomeScreen() + this.setRotation(testSpec.startRotation) + } + eachRun { + testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + testApp.postNotification(device, wmHelper) + device.pressHome() + wmHelper.waitForAppTransitionIdle() + } + } + + transitions { + var startY = 10 + var endY = 3 * device.displayHeight / 4 + var steps = 25 + if (openingNotificationsFromLockScreen) { + val wm = instrumentation.context.getSystemService(WindowManager::class.java) + val metricInsets = wm.currentWindowMetrics.windowInsets + val insets = metricInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.statusBars() + or WindowInsets.Type.displayCutout()) + + startY = insets.top + 100 + endY = device.displayHeight / 2 + steps = 4 + } + + // Swipe down to show the notification shade + val x = device.displayWidth / 2 + device.swipe(x, startY, x, endY, steps) + device.waitForIdle(2000) + instrumentation.uiAutomation.syncInputTransactions() + + // Launch the activity by clicking the notification + val notification = device.wait(Until.findObject( + By.text("Flicker Test Notification")), 2000L) + notification?.click() ?: error("Notification not found") + instrumentation.uiAutomation.syncInputTransactions() + + // Wait for the app to launch + wmHelper.waitForFullScreenApp(testApp.component) + } + + teardown { + test { + testApp.exit(wmHelper) + } + } + } + + @Test + @Postsubmit + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @Test + @Postsubmit + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() + + @Test + @Postsubmit + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + @Test + @Postsubmit + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Test + @Postsubmit + override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart() + + @Test + @Postsubmit + override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart() + + @Test + @Postsubmit + fun notificationAppWindowVisibleAtEnd() { + testSpec.assertWmEnd { + this.isAppWindowVisible(testApp.component) + } + } + + @Test + @Postsubmit + fun notificationAppWindowOnTopAtEnd() { + testSpec.assertWmEnd { + this.isAppWindowOnTop(testApp.component) + } + } + + @Test + @Postsubmit + fun notificationAppLayerVisibleAtEnd() { + testSpec.assertLayersEnd { + this.isVisible(testApp.component) + } + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appWindowBecomesTopWindow() { + Assume.assumeFalse(isShellTransitionsEnabled) + super.appWindowBecomesTopWindow() + } + + @FlakyTest(bugId = 229738092) + @Test + fun appWindowBecomesTopWindow_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + super.appWindowBecomesTopWindow() + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 3) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 663af703f76d..2226fd1d2155 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -16,18 +16,21 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice +import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Display import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -58,13 +61,15 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { +open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) + : OpenAppFromLauncherTransition(testSpec) { + /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper) @@ -73,25 +78,31 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio device.pressHome() wmHelper.waitForAppTransitionIdle() device.pressRecentApps() - wmHelper.waitForAppTransitionIdle() - this.setRotation(testSpec.config.startRotation) + wmHelper.waitFor( + WindowManagerConditionsFactory + .isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT), + WindowManagerConditionsFactory.hasLayersAnimating().negate() + ) + this.setRotation(testSpec.startRotation) } } transitions { device.reopenAppFromOverview(wmHelper) wmHelper.waitFor( - WindowManagerConditionsFactory.hasLayersAnimating().negate(), - WindowManagerConditionsFactory.isWMStateComplete(), - WindowManagerConditionsFactory.isHomeActivityVisible().negate() + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isWMStateComplete(), + WindowManagerConditionsFactory.isLayerVisible(LAUNCHER_COMPONENT).negate(), + WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT).negate() ) wmHelper.waitForFullScreenApp(testApp.component) } } /** {@inheritDoc} */ - @FlakyTest + @FlakyTest(bugId = 206753786) @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() /** {@inheritDoc} */ @Presubmit @@ -99,9 +110,9 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() /** {@inheritDoc} */ - @Presubmit + @FlakyTest @Test - override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible() + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() /** {@inheritDoc} */ @Presubmit @@ -113,6 +124,51 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio @Test override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 229735718) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowReplacesLauncherAsTopWindow() { + Assume.assumeFalse(isShellTransitionsEnabled) + super.appWindowReplacesLauncherAsTopWindow() + } + + @FlakyTest(bugId = 229738092) + @Test + fun appWindowReplacesLauncherAsTopWindow_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + super.appWindowReplacesLauncherAsTopWindow() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowBecomesTopWindow() { + Assume.assumeFalse(isShellTransitionsEnabled) + super.appWindowBecomesTopWindow() + } + + @FlakyTest(bugId = 229738092) + @Test + fun appWindowBecomesTopWindow_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + super.appWindowBecomesTopWindow() + } + companion object { /** * Creates the test configurations. @@ -124,7 +180,7 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 08aaea70762f..55eb3c3d1a0a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -17,20 +17,19 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice import android.view.Surface import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.navBarLayerPositionEnd import com.android.server.wm.traces.common.FlickerComponentName -import com.google.common.truth.Truth import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -40,6 +39,8 @@ import org.junit.runners.Parameterized /** * Test launching an app while the device is locked * + * This test assumes the device doesn't have AOD enabled + * * To run this test: `atest FlickerTests:OpenAppNonResizeableTest` * * Actions: @@ -59,87 +60,26 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { +open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) + : OpenAppFromLockTransition(testSpec) { override val testApp = NonResizeableAppHelper(instrumentation) private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#") /** - * Defines the transition used to run the test + * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation + * and remains visible at the end */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - get() = { args -> - super.transition(this, args) - setup { - eachRun { - device.sleep() - wmHelper.waitFor("noAppWindowsOnTop") { - it.wmState.topVisibleAppWindow.isEmpty() - } - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { - testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - } - } - - /** - * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation - * and becomes visible at the end - */ - @Postsubmit + @FlakyTest(bugId = 227083463) @Test fun navBarLayerVisibilityChanges() { testSpec.assertLayers { - this.isVisible(FlickerComponentName.NAV_BAR) - .then() - .isInvisible(FlickerComponentName.NAV_BAR) + this.isInvisible(FlickerComponentName.NAV_BAR) .then() .isVisible(FlickerComponentName.NAV_BAR) } } /** - * Checks that the app layer doesn't exist at the start of the transition, that it is - * created (invisible) and becomes visible during the transition - */ - @FlakyTest - @Test - fun appLayerBecomesVisible() { - testSpec.assertLayers { - this.notContains(testApp.component) - .then() - .isInvisible(testApp.component) - .then() - .isVisible(testApp.component) - } - } - - /** - * Checks that the app window doesn't exist at the start of the transition, that it is - * created (invisible - optional) and becomes visible during the transition - * - * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, - * the window may be visible or not depending on what was processed until that moment. - */ - @Presubmit - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - this.notContains(testApp.component) - .then() - .isAppWindowInvisible(testApp.component, isOptional = true) - .then() - .isAppWindowVisible(testApp.component) - } - } - - /** * Checks if [testApp] is visible at the end of the transition */ @Presubmit @@ -151,31 +91,23 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti } /** - * Checks that the nav bar starts the transition visible, then becomes invisible during - * then unlocking animation and becomes visible at the end of the transition + * Checks that the nav bar starts the transition invisible, then becomes visible during + * the unlocking animation and remains visible at the end of the transition */ - @Postsubmit + @Presubmit @Test fun navBarWindowsVisibilityChanges() { testSpec.assertWm { - this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) - .then() - .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) + this.isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) .then() .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) } } - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() - /** * Checks that the status bar layer is visible at the end of the trace * - * It is not possible to check at the start because the animation is working differently - * in devices with and without blur (b/202936526) + * It is not possible to check at the start because the screen is off */ @Presubmit @Test @@ -186,31 +118,31 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti } /** {@inheritDoc} */ - @FlakyTest(bugId = 202936526) + @FlakyTest(bugId = 206753786) @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() /** {@inheritDoc} */ - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerPositionAtEnd() { testSpec.assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") + ?: error("There is no display!") this.visibleRegion(FlickerComponentName.STATUS_BAR) .coversExactly(WindowUtils.getStatusBarPosition(display)) } } - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - @FlakyTest + /** + * Checks the position of the navigation bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() /** {@inheritDoc} */ @FlakyTest @@ -219,47 +151,14 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti super.visibleLayersShownMoreThanOneConsecutiveEntry() /** {@inheritDoc} */ - @Postsubmit - @Test - override fun entireScreenCovered() = super.entireScreenCovered() - - /** - * Checks that the focus changes from the launcher to [testApp] - */ @FlakyTest @Test - override fun focusChanges() = super.focusChanges() - - /** - * Checks that the screen is locked at the start of the transition ([colorFadComponent]) - * layer is visible - */ - @Postsubmit - @Test - fun screenLockedStart() { - testSpec.assertLayersStart { - isVisible(colorFadComponent) - } - } + override fun entireScreenCovered() = super.entireScreenCovered() - /** - * This test checks if the launcher is visible at the start and the app at the end, - * it cannot use the regular assertion (check over time), because on lock screen neither - * the app not the launcher are visible, and there is no top visible window. - */ - @Postsubmit + @FlakyTest(bugId = 218470989) @Test - override fun appWindowReplacesLauncherAsTopWindow() { - testSpec.assertWm { - this.invoke("noAppWindowsOnTop") { - Truth.assertWithMessage("Should not have any app window on top " + - "when the screen is locked") - .that(it.wmState.topVisibleAppWindow) - .isEmpty() - }.then() - .isAppWindowOnTop(testApp.component) - } - } + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { /** @@ -273,11 +172,11 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY), supportedRotations = listOf(Surface.ROTATION_0) ) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index 7af7b3ab6f24..20e6d0222854 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -21,19 +21,15 @@ import android.platform.test.annotations.Presubmit import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible @@ -50,18 +46,16 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { /** * Defines the transition used to run the test */ - protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } + protected open val transition: FlickerBuilder.() -> Unit = { setup { test { device.wakeUpAndGoToHomeScreen() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { test { - testApp.exit() + testApp.exit(wmHelper) } } } @@ -73,7 +67,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - transition(testSpec.config) + transition() } } @@ -155,52 +149,87 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { open fun entireScreenCovered() = testSpec.entireScreenCovered() /** - * Checks that the focus changes from the launcher to [testApp] + * Checks that the app layer doesn't exist or is invisible at the start of the transition, but + * is created and/or becomes visible during the transition. */ @Presubmit @Test - open fun focusChanges() { - testSpec.assertEventLog { - this.focusChanges("NexusLauncherActivity", testApp.`package`) + open fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart() + + protected fun appLayerBecomesVisible_coldStart() { + testSpec.assertLayers { + this.notContains(testApp.component) + .then() + .isInvisible(testApp.component, isOptional = true) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isVisible(testApp.component) } } - /** - * Checks that [LAUNCHER_COMPONENT] layer is visible at the start of the transition, and - * is replaced by [testApp], which remains visible until the end - */ - open fun appLayerReplacesLauncher() { - testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component) + protected fun appLayerBecomesVisible_warmStart() { + testSpec.assertLayers { + this.isInvisible(testApp.component) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isVisible(testApp.component) + } } /** - * Checks that [LAUNCHER_COMPONENT] window is visible at the start of the transition, and - * is replaced by a snapshot or splash screen (optional), and finally, is replaced by - * [testApp], which remains visible until the end + * Checks that the app window doesn't exist at the start of the transition, that it is + * created (invisible - optional) and becomes visible during the transition + * + * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, + * the window may be visible or not depending on what was processed until that moment. */ @Presubmit @Test - open fun appWindowReplacesLauncherAsTopWindow() { + open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart() + + protected fun appWindowBecomesVisible_coldStart() { testSpec.assertWm { - this.isAppWindowOnTop(LAUNCHER_COMPONENT) + this.notContains(testApp.component) .then() - .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .isAppWindowInvisible(testApp.component, isOptional = true) .then() - .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .isAppWindowVisible(testApp.component) + } + } + + protected fun appWindowBecomesVisible_warmStart() { + testSpec.assertWm { + this.isAppWindowInvisible(testApp.component) .then() - .isAppWindowOnTop(testApp.component) + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowVisible(testApp.component) } } /** - * Checks that [LAUNCHER_COMPONENT] window is visible at the start, and - * becomes invisible during the transition + * Checks that [testApp] window is not on top at the start of the transition, and then becomes + * the top visible window until the end of the transition. */ - open fun launcherWindowBecomesInvisible() { + @Presubmit + @Test + open fun appWindowBecomesTopWindow() { testSpec.assertWm { - this.isAppWindowOnTop(LAUNCHER_COMPONENT) + this.isAppWindowNotOnTop(testApp.component) + .then() + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) .then() - .isAppWindowNotOnTop(LAUNCHER_COMPONENT) + .isAppWindowOnTop(testApp.component) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index 5edee0cf0ca0..97528c0471cc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -17,15 +17,14 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.setRotation import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -55,13 +54,14 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { +open class OpenAppWarmTest(testSpec: FlickerTestParameter) + : OpenAppFromLauncherTransition(testSpec) { /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper) @@ -69,7 +69,7 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp eachRun { device.pressHome() wmHelper.waitForHomeActivityVisible() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -84,6 +84,23 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowReplacesLauncherAsTopWindow() = + super.appWindowReplacesLauncherAsTopWindow() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @@ -96,17 +113,26 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** {@inheritDoc} */ @Presubmit @Test - override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible() + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() /** {@inheritDoc} */ @Presubmit @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() /** {@inheritDoc} */ @Presubmit @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() + + @FlakyTest(bugId = 229735718) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() companion object { /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 495e2d62a11d..2f546b56f145 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -33,8 +33,6 @@ import com.android.server.wm.flicker.helpers.NewTasksAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME @@ -65,14 +63,16 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class TaskTransitionTest(val testSpec: FlickerTestParameter) { - val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation) + private val mWallpaper by lazy { + getWallpaperPackage(InstrumentationRegistry.getInstrumentation()) + ?: error("Unable to obtain wallpaper") + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { eachRun { mTestApp.launchViaIntent(wmHelper) @@ -101,7 +101,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @Test fun wallpaperWindowIsNeverVisible() { testSpec.assertWm { - this.isNonAppWindowInvisible(WALLPAPER) + this.isNonAppWindowInvisible(mWallpaper) } } @@ -113,7 +113,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @Test fun wallpaperLayerIsNeverVisible() { testSpec.assertLayers { - this.isInvisible(WALLPAPER) + this.isInvisible(mWallpaper) this.isInvisible(WALLPAPER_BBQ_WRAPPER) } } @@ -149,25 +149,31 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @Test fun colorLayerIsVisibleDuringTransition() { val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer") - val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) testSpec.assertLayers { - this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) + this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { + it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds) + } .isInvisible(bgColorLayer) .then() // Transitioning .isVisible(bgColorLayer) .then() // Fully transitioned to simple SIMPLE_ACTIVITY - .coversExactly(displayBounds, SIMPLE_ACTIVITY) + .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") { + it.visibleRegion(SIMPLE_ACTIVITY).coversExactly(displayBounds) + } .isInvisible(bgColorLayer) .then() // Transitioning back .isVisible(bgColorLayer) .then() // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY + .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { + it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds) + } .isInvisible(bgColorLayer) - .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) } } @@ -227,22 +233,21 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() companion object { - private val WALLPAPER = getWallpaperPackage(InstrumentationRegistry.getInstrumentation()) private val LAUNCH_NEW_TASK_ACTIVITY = LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent() private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() - private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName { + private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName? { val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext) - return wallpaperManager.wallpaperInfo.component.toFlickerComponent() + return wallpaperManager.wallpaperInfo?.component?.toFlickerComponent() } @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS new file mode 100644 index 000000000000..897fe5dee7fb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS @@ -0,0 +1,2 @@ +# System UI > ... > Launcher > Gesture nav +# Bug component: 565144 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index 52904cce8772..c89e6a44ab6c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -17,11 +17,12 @@ package com.android.server.wm.flicker.quickswitch import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.view.Surface import android.view.WindowManagerPolicyConstants import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -32,14 +33,15 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isRotated +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -62,18 +64,28 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) { +open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val taplInstrumentation = LauncherInstrumentation() private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { + test { + taplInstrumentation.setExpectedRotation(testSpec.startRotation) + } + eachRun { testApp1.launchViaIntent(wmHelper) wmHelper.waitForFullScreenApp(testApp1.component) @@ -83,20 +95,10 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet } } transitions { - // Swipe right from bottom to quick switch back - // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle - // as to not accidentally trigger a swipe back or forward action which would result - // in the same behavior but not testing quick swap. - device.swipe( - startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - 2 * startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - if (testSpec.config.startRotation.isRotated()) 75 else 30 - ) - + taplInstrumentation.launchedAppState.quickSwitchToPreviousApp() wmHelper.waitForFullScreenApp(testApp1.component) wmHelper.waitForAppTransitionIdle() + wmHelper.waitForNavBarStatusBarVisible() } teardown { @@ -112,7 +114,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that the transition starts with [testApp2]'s windows filling/covering exactly the * entirety of the display. */ - @Postsubmit + @Presubmit @Test fun startsWithApp2WindowsCoverFullScreen() { testSpec.assertWmStart { @@ -124,7 +126,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the * entirety of the display. */ - @Postsubmit + @Presubmit @Test fun startsWithApp2LayersCoverFullScreen() { testSpec.assertLayersStart { @@ -135,7 +137,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet /** * Checks that the transition starts with [testApp2] being the top window. */ - @Postsubmit + @Presubmit @Test fun startsWithApp2WindowBeingOnTop() { testSpec.assertWmStart { @@ -147,7 +149,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp1] windows fill the entire screen (i.e. is "fullscreen") at the end of the * transition once we have fully quick switched from [testApp2] back to the [testApp1]. */ - @Postsubmit + @Presubmit @Test fun endsWithApp1WindowsCoveringFullScreen() { testSpec.assertWmEnd { @@ -159,7 +161,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp1] layers fill the entire screen (i.e. is "fullscreen") at the end of the * transition once we have fully quick switched from [testApp2] back to the [testApp1]. */ - @Postsubmit + @Presubmit @Test fun endsWithApp1LayersCoveringFullScreen() { testSpec.assertLayersEnd { @@ -171,7 +173,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp1] is the top window at the end of the transition once we have fully quick * switched from [testApp2] back to the [testApp1]. */ - @Postsubmit + @Presubmit @Test fun endsWithApp1BeingOnTop() { testSpec.assertWmEnd { @@ -183,7 +185,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp1]'s window starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Postsubmit + @Presubmit @Test fun app1WindowBecomesAndStaysVisible() { testSpec.assertWm { @@ -199,7 +201,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp1]'s layer starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Postsubmit + @Presubmit @Test fun app1LayerBecomesAndStaysVisible() { testSpec.assertLayers { @@ -213,7 +215,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp2]'s window starts off visible and becomes invisible at some point before * the end of the transition and then stays invisible until the end of the transition. */ - @Postsubmit + @Presubmit @Test fun app2WindowBecomesAndStaysInvisible() { testSpec.assertWm { @@ -227,7 +229,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Checks that [testApp2]'s layer starts off visible and becomes invisible at some point before * the end of the transition and then stays invisible until the end of the transition. */ - @Postsubmit + @Presubmit @Test fun app2LayerBecomesAndStaysInvisible() { testSpec.assertLayers { @@ -242,7 +244,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Ensures that at any point, either [testApp1] or [testApp2]'s windows are at least partially * visible. */ - @Postsubmit + @Presubmit @Test fun app1WindowIsVisibleOnceApp2WindowIsInvisible() { testSpec.assertWm { @@ -262,7 +264,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * Ensures that at any point, either [testApp1] or [testApp2]'s windows are at least partially * visible. */ - @Postsubmit + @Presubmit @Test fun app1LayerIsVisibleOnceApp2LayerIsInvisible() { testSpec.assertLayers { @@ -279,14 +281,14 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet /** * Checks that the navbar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() /** * Checks that the navbar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() @@ -295,21 +297,21 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * * NOTE: This doesn't check that the navbar is visible or not. */ - @Postsubmit + @Presubmit @Test fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() /** * Checks that the status bar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() @@ -319,7 +321,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), @@ -327,4 +329,4 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet ) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt new file mode 100644 index 000000000000..b9fef085da29 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt @@ -0,0 +1,55 @@ +/* + * 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.server.wm.flicker.quickswitch + +import android.platform.test.annotations.RequiresDevice +import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching back to previous app from last opened app + * + * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` + * + * Actions: + * Launch an app [testApp1] + * Launch another app [testApp2] + * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] + * + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@FlakyTest(bugId = 228009808) +open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter) + : QuickSwitchBetweenTwoAppsBackTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 842aa2b548db..725d2c3d818c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -17,11 +17,12 @@ package com.android.server.wm.flicker.quickswitch import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.view.Surface import android.view.WindowManagerPolicyConstants import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -31,16 +32,16 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isRotated +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.Rect +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -50,7 +51,7 @@ import org.junit.runners.Parameterized /** * Test quick switching back to previous app from last opened app * - * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` + * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest` * * Actions: * Launch an app [testApp1] @@ -63,20 +64,26 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) { +open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val taplInstrumentation = LauncherInstrumentation() private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { + test { + taplInstrumentation.setExpectedRotation(testSpec.startRotation) + } + eachRun { testApp1.launchViaIntent(wmHelper) wmHelper.waitForFullScreenApp(testApp1.component) @@ -84,43 +91,29 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara testApp2.launchViaIntent(wmHelper) wmHelper.waitForFullScreenApp(testApp2.component) - // Swipe right from bottom to quick switch back - // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle - // as to not accidentally trigger a swipe back or forward action which would result - // in the same behavior but not testing quick swap. - device.swipe( - startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - 2 * startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - if (testSpec.config.startRotation.isRotated()) 75 else 30 - ) + startDisplayBounds = wmHelper.currentState.layerState + .displays.firstOrNull { !it.isVirtual } + ?.layerStackSpace + ?: error("Display not found") + + taplInstrumentation.launchedAppState.quickSwitchToPreviousApp() wmHelper.waitForFullScreenApp(testApp1.component) wmHelper.waitForAppTransitionIdle() } } transitions { - // Swipe left from bottom to quick switch forward - // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle - // as to not accidentally trigger a swipe back or forward action which would result - // in the same behavior but not testing quick swap. - device.swipe( - 2 * startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - if (testSpec.config.startRotation.isRotated()) 75 else 30 - ) + taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft() wmHelper.waitForFullScreenApp(testApp2.component) wmHelper.waitForAppTransitionIdle() + wmHelper.waitForNavBarStatusBarVisible() } teardown { test { - testApp1.exit() - testApp2.exit() + testApp1.exit(wmHelper) + testApp2.exit(wmHelper) } } } @@ -130,11 +123,12 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the * entirety of the display. */ - @Postsubmit + @Presubmit @Test - fun startsWithApp1WindowsCoverFullScreen() { + open fun startsWithApp1WindowsCoverFullScreen() { testSpec.assertWmStart { - this.frameRegion(testApp1.component).coversExactly(startDisplayBounds) + this.frameRegion(testApp1.component, FlickerComponentName.LETTERBOX) + .coversExactly(startDisplayBounds) } } @@ -142,9 +136,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the * entirety of the display. */ - @Postsubmit + @Presubmit @Test - fun startsWithApp1LayersCoverFullScreen() { + open fun startsWithApp1LayersCoverFullScreen() { testSpec.assertLayersStart { this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds) } @@ -153,21 +147,21 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara /** * Checks that the transition starts with [testApp1] being the top window. */ - @Postsubmit + @Presubmit @Test - fun startsWithApp1WindowBeingOnTop() { + open fun startsWithApp1WindowBeingOnTop() { testSpec.assertWmStart { this.isAppWindowOnTop(testApp1.component) } } /** - * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of the - * transition once we have fully quick switched from [testApp1] back to the [testApp2]. + * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of + * the transition once we have fully quick switched from [testApp1] back to the [testApp2]. */ - @Postsubmit + @Presubmit @Test - fun endsWithApp2WindowsCoveringFullScreen() { + open fun endsWithApp2WindowsCoveringFullScreen() { testSpec.assertWmEnd { this.frameRegion(testApp2.component).coversExactly(startDisplayBounds) } @@ -177,21 +171,22 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the * transition once we have fully quick switched from [testApp1] back to the [testApp2]. */ - @Postsubmit + @Presubmit @Test - fun endsWithApp2LayersCoveringFullScreen() { + open fun endsWithApp2LayersCoveringFullScreen() { testSpec.assertLayersEnd { - this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds) + this.visibleRegion(testApp2.component, FlickerComponentName.LETTERBOX) + .coversExactly(startDisplayBounds) } } /** - * Checks that [testApp2] is the top window at the end of the transition once we have fully quick - * switched from [testApp1] back to the [testApp2]. + * Checks that [testApp2] is the top window at the end of the transition once we have fully + * quick switched from [testApp1] back to the [testApp2]. */ - @Postsubmit + @Presubmit @Test - fun endsWithApp2BeingOnTop() { + open fun endsWithApp2BeingOnTop() { testSpec.assertWmEnd { this.isAppWindowOnTop(testApp2.component) } @@ -201,9 +196,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2]'s window starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Postsubmit + @Presubmit @Test - fun app2WindowBecomesAndStaysVisible() { + open fun app2WindowBecomesAndStaysVisible() { testSpec.assertWm { this.isAppWindowInvisible(testApp2.component) .then() @@ -217,9 +212,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Postsubmit + @Presubmit @Test - fun app2LayerBecomesAndStaysVisible() { + open fun app2LayerBecomesAndStaysVisible() { testSpec.assertLayers { this.isInvisible(testApp2.component) .then() @@ -231,9 +226,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp1]'s window starts off visible and becomes invisible at some point before * the end of the transition and then stays invisible until the end of the transition. */ - @Postsubmit + @Presubmit @Test - fun app1WindowBecomesAndStaysInvisible() { + open fun app1WindowBecomesAndStaysInvisible() { testSpec.assertWm { this.isAppWindowVisible(testApp1.component) .then() @@ -245,9 +240,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp1]'s layer starts off visible and becomes invisible at some point before * the end of the transition and then stays invisible until the end of the transition. */ - @Postsubmit + @Presubmit @Test - fun app1LayerBecomesAndStaysInvisible() { + open fun app1LayerBecomesAndStaysInvisible() { testSpec.assertLayers { this.isVisible(testApp1.component) .then() @@ -260,9 +255,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially * visible. */ - @Postsubmit + @Presubmit @Test - fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { + open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { testSpec.assertWm { this.isAppWindowVisible(testApp1.component) .then() @@ -279,9 +274,9 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially * visible. */ - @Postsubmit + @Presubmit @Test - fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { + open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { testSpec.assertLayers { this.isVisible(testApp1.component) .then() @@ -296,47 +291,59 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara /** * Checks that the navbar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() + open fun navBarWindowIsAlwaysVisible() { + testSpec.navBarWindowIsVisible() + } /** * Checks that the navbar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test - fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() + open fun navBarLayerAlwaysIsVisible() { + testSpec.navBarLayerIsVisible() + } /** * Checks that the navbar is always in the right position and covers the expected region. * * NOTE: This doesn't check that the navbar is visible or not. */ - @Postsubmit + @Presubmit @Test - fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() + open fun navbarIsAlwaysInRightPosition() { + testSpec.navBarLayerRotatesAndScales() + } /** * Checks that the status bar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() + open fun statusBarWindowIsAlwaysVisible() { + testSpec.statusBarWindowIsVisible() + } /** * Checks that the status bar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() + open fun statusBarLayerIsAlwaysVisible() { + testSpec.statusBarLayerIsVisible() + } companion object { + private var startDisplayBounds = Rect.EMPTY + @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), @@ -344,4 +351,4 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara ) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt new file mode 100644 index 000000000000..4b8a8c80cd45 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt @@ -0,0 +1,55 @@ +/* + * 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.server.wm.flicker.quickswitch + +import android.platform.test.annotations.RequiresDevice +import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching back to previous app from last opened app + * + * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest` + * + * Actions: + * Launch an app [testApp1] + * Launch another app [testApp2] + * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] + * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2] + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +@FlakyTest(bugId = 228009808) +open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter) + : QuickSwitchBetweenTwoAppsForwardTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index 10ca0d9b323b..cc4a4b2d38aa 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -22,12 +22,13 @@ import android.platform.test.annotations.RequiresDevice import android.view.Surface import android.view.WindowManagerPolicyConstants import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.SimpleAppHelper @@ -35,7 +36,6 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -60,16 +60,23 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 +@Group1 class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val taplInstrumentation = LauncherInstrumentation() + private val testApp = SimpleAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { + test { + taplInstrumentation.setExpectedRotation(testSpec.startRotation) + } + eachRun { testApp.launchViaIntent(wmHelper) device.pressHome() @@ -78,20 +85,10 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { } } transitions { - // Swipe right from bottom to quick switch back - // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle - // as to not accidentally trigger a swipe back or forward action which would result - // in the same behavior but not testing quick swap. - device.swipe( - startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - 2 * startDisplayBounds.bounds.right / 3, - startDisplayBounds.bounds.bottom, - 50 - ) - + taplInstrumentation.workspace.quickSwitchToPreviousApp() wmHelper.waitForFullScreenApp(testApp.component) wmHelper.waitForAppTransitionIdle() + wmHelper.waitForNavBarStatusBarVisible() } teardown { @@ -262,7 +259,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { testSpec.assertWm { this.isAppWindowOnTop(LAUNCHER_COMPONENT) .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT) + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp.component) } @@ -278,7 +275,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { testSpec.assertLayers { this.isVisible(LAUNCHER_COMPONENT) .then() - .isVisible(FlickerComponentName.SNAPSHOT) + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() .isVisible(testApp.component) } @@ -334,7 +331,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), @@ -343,4 +340,4 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { ) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index fd8abc621b33..f0d16f3edb11 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.rotation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -26,13 +25,11 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -81,13 +78,10 @@ import org.junit.runners.Parameterized class ChangeAppRotationTest( testSpec: FlickerTestParameter ) : RotationTransition(testSpec) { - @get:Rule - val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) - override val testApp = SimpleAppHelper(instrumentation) - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper) @@ -95,38 +89,23 @@ class ChangeAppRotationTest( } } - @Postsubmit - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @Postsubmit - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - - /** {@inheritDoc} */ - @FlakyTest(bugId = 190185577) + /** + * Windows maybe recreated when rotated. Checks that the focus does not change or if it does, + * focus returns to [testApp] + */ + @Presubmit @Test - override fun focusDoesNotChange() { - super.focusDoesNotChange() + fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges(testApp.`package`) + } } /** * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, * doesn't flicker, and disappears before the transition is complete */ - @Presubmit - @Test - fun rotationLayerAppearsAndVanishes() { + fun rotationLayerAppearsAndVanishesAssertion() { testSpec.assertLayers { this.isVisible(testApp.component) .then() @@ -138,6 +117,16 @@ class ChangeAppRotationTest( } /** + * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, + * doesn't flicker, and disappears before the transition is complete + */ + @Presubmit + @Test + fun rotationLayerAppearsAndVanishes() { + rotationLayerAppearsAndVanishesAssertion() + } + + /** * Checks that the status bar window is visible and above the app windows in all WM * trace entries */ @@ -159,7 +148,7 @@ class ChangeAppRotationTest( /** * Checks the position of the status bar at the start and end of the transition */ - @Presubmit + @FlakyTest(bugId = 206753786) @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @@ -187,7 +176,7 @@ class ChangeAppRotationTest( @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigRotationTests(repetitions = 5) + .getConfigRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS new file mode 100644 index 000000000000..f7c0a87f5dac --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS @@ -0,0 +1,2 @@ +# window manager > animations/transitions +# Bug component: 316275 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index e850632ed8af..0becadf630e1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -22,14 +22,12 @@ import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.startRotation import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test @@ -41,10 +39,10 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + protected open val transition: FlickerBuilder.() -> Unit = { setup { eachRun { - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -53,7 +51,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } transitions { - this.setRotation(testSpec.config.endRotation) + this.setRotation(testSpec.endRotation) } } @@ -64,7 +62,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - transition(testSpec.config) + transition() } } @@ -131,17 +129,6 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) open fun entireScreenCovered() = testSpec.entireScreenCovered() /** - * Checks that the focus doesn't change during animation - */ - @Presubmit - @Test - open fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } - } - - /** * Checks that [testApp] layer covers the entire screen at the start of the transition */ @Presubmit @@ -166,4 +153,4 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 310f04b9710f..fac5baf7a2f9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -17,9 +17,9 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice import android.view.WindowManager import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -76,19 +76,19 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -class SeamlessAppRotationTest( +open class SeamlessAppRotationTest( testSpec: FlickerTestParameter ) : RotationTransition(testSpec) { override val testApp = SeamlessRotationAppHelper(instrumentation) - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper, - stringExtras = mapOf( - ActivityOptions.EXTRA_STARVE_UI_THREAD to it.starveUiThread.toString()) + stringExtras = mapOf(ActivityOptions.EXTRA_STARVE_UI_THREAD + to testSpec.starveUiThread.toString()) ) } } @@ -179,21 +179,34 @@ class SeamlessAppRotationTest( } } + /** + * Checks that the focus doesn't change during animation + */ + @Presubmit + @Test + fun focusDoesNotChange() { + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() companion object { - private val Map<String, Any?>.starveUiThread - get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean + private val FlickerTestParameter.starveUiThread + get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean - private fun FlickerTestParameter.createConfig( + private fun createConfig( + sourceConfig: FlickerTestParameter, starveUiThread: Boolean - ): MutableMap<String, Any?> { - val config = this.config.toMutableMap() - config[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread - return config + ): FlickerTestParameter { + val newConfig = sourceConfig.config.toMutableMap() + .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread } + val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else "" + return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt") } /** @@ -206,15 +219,10 @@ class SeamlessAppRotationTest( private fun getConfigurations(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigRotationTests(repetitions = 2) - .flatMap { - val defaultRun = it.createConfig(starveUiThread = false) - val busyUiRun = it.createConfig(starveUiThread = true) - listOf( - FlickerTestParameter(defaultRun), - FlickerTestParameter(busyUiRun, - name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD" - ) - ) + .flatMap { sourceConfig -> + val defaultRun = createConfig(sourceConfig, starveUiThread = false) + val busyUiRun = createConfig(sourceConfig, starveUiThread = true) + listOf(defaultRun, busyUiRun) } } diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index cb37fc7b47e9..43aa4b151548 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -23,6 +23,7 @@ android:supportsRtl="true"> <activity android:name=".SimpleActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" + android:theme="@style/CutoutShortEdges" android:label="SimpleApp" android:exported="true"> <intent-filter> @@ -32,6 +33,7 @@ </activity> <activity android:name=".ImeActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" + android:theme="@style/CutoutShortEdges" android:label="ImeApp" android:exported="true"> <intent-filter> @@ -40,6 +42,7 @@ </intent-filter> </activity> <activity android:name=".ImeActivityAutoFocus" + android:theme="@style/CutoutShortEdges" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" android:windowSoftInputMode="stateVisible" android:label="ImeAppAutoFocus" @@ -49,8 +52,19 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".ImeStateInitializeActivity" + android:theme="@style/no_starting_window" + android:windowSoftInputMode="stateAlwaysHidden" + android:label="ImeStateInitializeActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <activity android:name=".SeamlessRotationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" + android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="SeamlessApp" android:exported="true"> @@ -60,6 +74,7 @@ </intent-filter> </activity> <activity android:name=".NonResizeableActivity" + android:theme="@style/CutoutShortEdges" android:resizeableActivity="false" android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" android:label="NonResizeableApp" @@ -72,6 +87,7 @@ </activity> <activity android:name=".ButtonActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity" + android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="ButtonActivity" android:exported="true"> @@ -82,6 +98,7 @@ </activity> <activity android:name=".LaunchNewTaskActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity" + android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="LaunchNewTaskActivity" android:exported="true"> @@ -90,5 +107,60 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".DialogThemedActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/DialogTheme" + android:label="DialogThemedActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".PortraitOnlyActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" + android:theme="@style/CutoutShortEdges" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".ImeEditorPopupDialogActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/CutoutShortEdges" + android:label="ImeEditorPopupDialogActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".ShowWhenLockedActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="ShowWhenLockedActivity" + android:showWhenLocked="true" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".NotificationActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_notification.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_notification.xml new file mode 100644 index 000000000000..09bd44c10865 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_notification.xml @@ -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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/> +</vector> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index 2620ff407efc..baaf7073b3a6 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -31,4 +31,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Finish activity" /> + <Button + android:id="@+id/start_dialog_themed_activity_btn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Start dialog themed activity" /> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml new file mode 100644 index 000000000000..78072006f681 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_orange_light"> + <Button + android:id="@+id/post_notification" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Post Notification" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml new file mode 100644 index 000000000000..1d21fd56a487 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -0,0 +1,49 @@ +<?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. + --> + +<resources> + <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowBackground">@android:color/darker_gray</item> + </style> + + <style name="CutoutDefault" parent="@style/DefaultTheme"> + <item name="android:windowLayoutInDisplayCutoutMode">default</item> + </style> + + <style name="CutoutShortEdges" parent="@style/DefaultTheme"> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> + + <style name="CutoutNever" parent="@style/DefaultTheme"> + <item name="android:windowLayoutInDisplayCutoutMode">never</item> + </style> + + <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowAnimationStyle">@null</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@null</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowSoftInputMode">stateUnchanged</item> + </style> + + <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowDisablePreview">true</item> + </style> +</resources> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index baf36ab0e132..6cda482dd30a 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -37,6 +37,11 @@ public class ActivityOptions { new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeActivity"); + public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity"; + public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity"); + public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp"; public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, @@ -56,4 +61,30 @@ public class ActivityOptions { public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); + + public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity"; + public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".DialogThemedActivity"); + + public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity"; + public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".PortraitOnlyActivity"); + + public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME = + "ImeEditorPopupDialogActivity"; + public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity"); + + public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp"; + public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity"); + + public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp"; + public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NotificationActivity"); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java new file mode 100644 index 000000000000..20eb295d3e6b --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java @@ -0,0 +1,75 @@ +/* + * 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.server.wm.flicker.testapp; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.graphics.Color; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class DialogThemedActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_simple); + getWindow().addFlags(FLAG_NOT_FOCUSABLE); + getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT); + TextView textView = new TextView(this); + // Print SystemBars' insets visibility on this window for demonstrating during the test. + textView.setId(android.R.id.text1); + textView.setText("Insets visibility\n\n"); + textView.setTextColor(Color.BLACK); + LinearLayout layout = new LinearLayout(this); + layout.setBackgroundColor(Color.GREEN); + layout.addView(textView); + + // Create a dialog with dialog-themed activity + AlertDialog dialog = new AlertDialog.Builder(this) + .setView(layout) + .setTitle("Dialog for test") + .create(); + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(MATCH_PARENT, + MATCH_PARENT); + attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM; + dialog.getWindow().getDecorView().setLayoutParams(attrs); + dialog.setCanceledOnTouchOutside(true); + dialog.setOnShowListener(d -> textView.setText(textView.getText() + + "IME: " + isInsetsVisible(dialog, ime()) + "\n" + + "StatusBar: " + isInsetsVisible(dialog, statusBars()) + "\n" + + "NavBar: " + isInsetsVisible(dialog, navigationBars()) + "\n") + ); + dialog.show(); + dialog.setOnDismissListener((d) -> finish()); + } + + private String isInsetsVisible(Dialog d, int type) { + return d.getWindow().getDecorView().getRootWindowInsets().isVisible(type) ? "VISIBLE" + : "INVISIBLE"; + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java index 05da717620aa..bb200f125507 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java @@ -16,6 +16,8 @@ package com.android.server.wm.flicker.testapp; +import android.content.Intent; +import android.widget.Button; import android.widget.EditText; public class ImeActivityAutoFocus extends ImeActivity { @@ -26,5 +28,9 @@ public class ImeActivityAutoFocus extends ImeActivity { EditText editTextField = findViewById(R.id.plain_text_input); editTextField.requestFocus(); + + Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn); + startThemedActivityButton.setOnClickListener( + button -> startActivity(new Intent(this, DialogThemedActivity.class))); } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java new file mode 100644 index 000000000000..a8613f531e1c --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java @@ -0,0 +1,46 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.app.AlertDialog; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.LinearLayout; + +public class ImeEditorPopupDialogActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + setContentView(R.layout.activity_simple); + + final EditText editText = new EditText(this); + editText.setHint("focused editText"); + final AlertDialog dialog = new AlertDialog.Builder(this) + .setView(editText) + .setPositiveButton("Dismiss", (d, which) -> d.dismiss()) + .create(); + dialog.show(); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java new file mode 100644 index 000000000000..4be79c4f7bad --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java @@ -0,0 +1,50 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; + +/** + * A nop {@link Activity} to make sure that the test starts from a deterministic state. + * + * <p>Currently this {@link Activity} makes sure the following things</p> + * <li> + * <ul>Hide the software keyboard with + * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul> + * <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}. + * </ul> + * </li> + */ +public class ImeStateInitializeActivity extends Activity { + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + final View view = new View(this); + view.setBackgroundColor(Color.WHITE); + view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + // Make sure that navigation bar is rendered with black (if supported). + getWindow().setNavigationBarColor(Color.BLACK); + + setContentView(view); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java new file mode 100644 index 000000000000..b31af385d363 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -0,0 +1,80 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.TaskStackBuilder; +import android.content.Intent; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.Button; + +public class NotificationActivity extends Activity { + private static final String CHANNEL_ID = "notification_channel"; + private static final int NOTIFICATION_ID = 1; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.notification_button); + + Button button = findViewById(R.id.post_notification); + button.setOnClickListener(v -> postNotification()); + + createNotificationChannel(); + } + + private void postNotification() { + Intent resultIntent = new Intent(this, NotificationActivity.class); + TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); + stackBuilder.addNextIntentWithParentStack(resultIntent); + PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent(0, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle("Flicker Test Notification") + .setContentText("Flicker Test Notification") + // Set the intent that will fire when the user taps the notification + .setContentIntent(resultPendingIntent) + .setAutoCancel(true); + + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.notify(NOTIFICATION_ID, builder.build()); + } + + private void createNotificationChannel() { + CharSequence name = "channel_name"; + String description = "channel_description"; + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java new file mode 100644 index 000000000000..b1876b5e5511 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java @@ -0,0 +1,33 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; +import android.view.WindowManager; + +public class PortraitOnlyActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_simple); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java new file mode 100644 index 000000000000..6f94b74ccf41 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java @@ -0,0 +1,33 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; +import android.view.WindowManager; + +public class ShowWhenLockedActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_simple); + } +} diff --git a/tests/benchmarks/Android.bp b/tests/HandwritingIme/Android.bp index f87ca2ef928b..1f552bf4dc6d 100644 --- a/tests/benchmarks/Android.bp +++ b/tests/HandwritingIme/Android.bp @@ -1,4 +1,4 @@ -// Copyright (C) 2015 The Android Open Source Project +// 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. @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// build framework base core benchmarks -// ============================================================ - package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -24,15 +21,15 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -java_library { - name: "networkStatsFactory-benchmarks", - installable: true, - +android_test { + name: "HandwritingIme", srcs: ["src/**/*.java"], - - libs: [ - "caliper-api-target", - "services.core", + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + static_libs: [ + "androidx.core_core", + "androidx.appcompat_appcompat", + "com.google.android.material_material", ], - } diff --git a/tests/HandwritingIme/AndroidManifest.xml b/tests/HandwritingIme/AndroidManifest.xml new file mode 100644 index 000000000000..1445d95c2879 --- /dev/null +++ b/tests/HandwritingIme/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (018C) 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="com.google.android.test.handwritingime"> + + <application android:label="Handwriting IME"> + <service android:name=".HandwritingIme" + android:process=":HandwritingIme" + android:label="Handwriting IME" + android:permission="android.permission.BIND_INPUT_METHOD" + android:exported="true"> + <intent-filter> + <action android:name="android.view.InputMethod"/> + </intent-filter> + <meta-data android:name="android.view.im" + android:resource="@xml/ime"/> + </service> + + </application> +</manifest> diff --git a/tests/HandwritingIme/res/xml/ime.xml b/tests/HandwritingIme/res/xml/ime.xml new file mode 100644 index 000000000000..2e84a0389429 --- /dev/null +++ b/tests/HandwritingIme/res/xml/ime.xml @@ -0,0 +1,20 @@ +<?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. + --> + +<!-- Configuration info for an input method --> +<input-method xmlns:android="http://schemas.android.com/apk/res/android" + android:supportsStylusHandwriting="true"/>
\ No newline at end of file diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java new file mode 100644 index 000000000000..bf8bd14645a0 --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java @@ -0,0 +1,121 @@ +/* + * 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.google.android.test.handwritingime; + +import android.annotation.Nullable; +import android.inputmethodservice.InputMethodService; +import android.util.Log; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.Random; + +public class HandwritingIme extends InputMethodService { + + public static final int HEIGHT_DP = 100; + + private Window mInkWindow; + private InkView mInk; + + static final String TAG = "HandwritingIme"; + + interface HandwritingFinisher { + void finish(); + } + + interface StylusListener { + void onStylusEvent(MotionEvent me); + } + + final class StylusConsumer implements StylusListener { + @Override + public void onStylusEvent(MotionEvent me) { + HandwritingIme.this.onStylusEvent(me); + } + } + + final class HandwritingFinisherImpl implements HandwritingFinisher { + + HandwritingFinisherImpl() {} + + @Override + public void finish() { + finishStylusHandwriting(); + Log.d(TAG, "HandwritingIme called finishStylusHandwriting() "); + } + } + + private void onStylusEvent(@Nullable MotionEvent event) { + // TODO Hookup recognizer here + if (event.getAction() == MotionEvent.ACTION_UP) { + sendKeyChar((char) (56 + new Random().nextInt(66))); + } + } + + @Override + public View onCreateInputView() { + Log.d(TAG, "onCreateInputView"); + final ViewGroup view = new FrameLayout(this); + final View inner = new View(this); + final float density = getResources().getDisplayMetrics().density; + final int height = (int) (HEIGHT_DP * density); + view.setPadding(0, 0, 0, 0); + view.addView(inner, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, height)); + TextView text = new TextView(this); + text.setText("Handwriting IME"); + text.setTextSize(13f); + text.setTextColor(getColor(android.R.color.white)); + text.setGravity(Gravity.CENTER); + text.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, height)); + view.addView(text); + inner.setBackgroundColor(0xff0110fe); // blue + + return view; + } + + public void onPrepareStylusHandwriting() { + Log.d(TAG, "onPrepareStylusHandwriting "); + if (mInk == null) { + mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer()); + } + } + + @Override + public boolean onStartStylusHandwriting() { + Log.d(TAG, "onStartStylusHandwriting "); + Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show(); + mInkWindow = getStylusHandwritingWindow(); + mInkWindow.setContentView(mInk, mInk.getLayoutParams()); + return true; + } + + @Override + public void onFinishStylusHandwriting() { + Log.d(TAG, "onFinishStylusHandwriting "); + Toast.makeText(this, "Finish HW", Toast.LENGTH_SHORT).show(); + // Free-up + ((ViewGroup) mInk.getParent()).removeView(mInk); + mInk = null; + } +} diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java new file mode 100644 index 000000000000..87a5b900cc18 --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java @@ -0,0 +1,168 @@ +/* + * 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.google.android.test.handwritingime; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +class InkView extends View { + private static final long FINISH_TIMEOUT = 2500; + private final HandwritingIme.HandwritingFinisher mHwCanceller; + private final HandwritingIme.StylusConsumer mConsumer; + private final int mTopInset; + private Paint mPaint; + private Path mPath; + private float mX, mY; + private static final float STYLUS_MOVE_TOLERANCE = 1; + private Runnable mFinishRunnable; + + InkView(Context context, HandwritingIme.HandwritingFinisher hwController, + HandwritingIme.StylusConsumer consumer) { + super(context); + mHwCanceller = hwController; + mConsumer = consumer; + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + mPaint.setColor(Color.GREEN); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.ROUND); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStrokeWidth(14); + + mPath = new Path(); + + WindowManager wm = context.getSystemService(WindowManager.class); + WindowMetrics metrics = wm.getCurrentWindowMetrics(); + Insets insets = metrics.getWindowInsets() + .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + setLayoutParams(new ViewGroup.LayoutParams( + metrics.getBounds().width() - insets.left - insets.right, + metrics.getBounds().height() - insets.top - insets.bottom)); + mTopInset = insets.top; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawPath(mPath, mPaint); + canvas.drawARGB(20, 255, 50, 50); + } + + private void stylusStart(float x, float y) { + y = y - mTopInset; + mPath.moveTo(x, y); + mX = x; + mY = y; + } + + private void stylusMove(float x, float y) { + y = y - mTopInset; + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (mPath.isEmpty()) { + stylusStart(x, y); + } + if (dx >= STYLUS_MOVE_TOLERANCE || dy >= STYLUS_MOVE_TOLERANCE) { + mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + } + + private void stylusFinish() { + mPath.lineTo(mX, mY); + // TODO: support offscreen? e.g. mCanvas.drawPath(mPath, mPaint); + mPath.reset(); + mX = 0; + mY = 0; + + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) { + mConsumer.onStylusEvent(event); + android.util.Log.w(HandwritingIme.TAG, "INK touch onStylusEvent " + event); + float x = event.getX(); + float y = event.getY(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + cancelTimer(); + stylusStart(x, y); + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + stylusMove(x, y); + invalidate(); + break; + + case MotionEvent.ACTION_UP: + scheduleTimer(); + break; + + } + return true; + } + return false; + } + + private void cancelTimer() { + if (mFinishRunnable != null) { + if (getHandler() != null) { + getHandler().removeCallbacks(mFinishRunnable); + } + mFinishRunnable = null; + } + if (getHandler() != null) { + getHandler().removeCallbacksAndMessages(null); + } + } + + private void scheduleTimer() { + cancelTimer(); + if (getHandler() != null) { + postDelayed(getFinishRunnable(), FINISH_TIMEOUT); + } + } + + private Runnable getFinishRunnable() { + mFinishRunnable = () -> { + android.util.Log.e(HandwritingIme.TAG, "Hw view timer finishHandwriting "); + mHwCanceller.finish(); + stylusFinish(); + mPath.reset(); + invalidate(); + }; + + return mFinishRunnable; + } + +} diff --git a/tests/HwAccelerationTest/.project b/tests/HwAccelerationTest/.project deleted file mode 100644 index 7c04d3cb6426..000000000000 --- a/tests/HwAccelerationTest/.project +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<projectDescription> - <name>HwAccelerationTest</name> - <comment></comment> - <projects> - </projects> - <buildSpec> - <buildCommand> - <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>org.eclipse.jdt.core.javabuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.ApkBuilder</name> - <arguments> - </arguments> - </buildCommand> - </buildSpec> - <natures> - <nature>com.android.ide.eclipse.adt.AndroidNature</nature> - <nature>org.eclipse.jdt.core.javanature</nature> - </natures> -</projectDescription> diff --git a/tests/HwAccelerationTest/Android.bp b/tests/HwAccelerationTest/Android.bp index 76063227eac1..51848f2857c9 100644 --- a/tests/HwAccelerationTest/Android.bp +++ b/tests/HwAccelerationTest/Android.bp @@ -25,7 +25,17 @@ package { android_test { name: "HwAccelerationTest", - srcs: ["**/*.java"], + jni_libs: [ + "libhwaccelerationtest_jni", + ], + srcs: [ + "**/*.java", + "**/*.kt", + ], + static_libs: [ + "androidx.cardview_cardview", + ], + platform_apis: true, certificate: "platform", } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 04a55d6038b0..b0ccbd1cf22f 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -436,6 +436,15 @@ </intent-filter> </activity> + <activity android:name=".PenStylusActivity" + android:label="Pen/Draw" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="GLTextureViewActivity" android:label="TextureView/OpenGL" android:exported="true"> @@ -780,6 +789,15 @@ </intent-filter> </activity> + <activity android:name="RenderEffectViewActivity" + android:label="RenderEffect/View" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="StretchShaderActivity" android:label="RenderEffect/Stretch" android:exported="true"> diff --git a/tests/HwAccelerationTest/jni/Android.bp b/tests/HwAccelerationTest/jni/Android.bp new file mode 100644 index 000000000000..8edddab0ad1f --- /dev/null +++ b/tests/HwAccelerationTest/jni/Android.bp @@ -0,0 +1,44 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test_library { + + name: "libhwaccelerationtest_jni", + + cflags: [ + "-Werror", + "-Wno-error=deprecated-declarations", + ], + + gtest: false, + + srcs: [ + "native-lib.cpp", + ], + + shared_libs: [ + "libnativehelper", + "libandroid", + "liblog", + ], + + stl: "c++_static", + + sdk_version: "current", + +} diff --git a/tests/HwAccelerationTest/jni/native-lib.cpp b/tests/HwAccelerationTest/jni/native-lib.cpp new file mode 100644 index 000000000000..407d4bf76336 --- /dev/null +++ b/tests/HwAccelerationTest/jni/native-lib.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/hardware_buffer.h> +#include <android/hardware_buffer_jni.h> +#include <android/native_window.h> +#include <android/native_window_jni.h> +#include <android/surface_control.h> +#include <jni.h> + +struct MyWrapper { + MyWrapper(ANativeWindow* parent) { + surfaceControl = ASurfaceControl_createFromWindow(parent, "PenLayer"); + } + + ~MyWrapper() { ASurfaceControl_release(surfaceControl); } + + void setBuffer(AHardwareBuffer* buffer) { + ASurfaceTransaction* transaction = ASurfaceTransaction_create(); + ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer); + ASurfaceTransaction_setVisibility(transaction, surfaceControl, + ASURFACE_TRANSACTION_VISIBILITY_SHOW); + ASurfaceTransaction_apply(transaction); + ASurfaceTransaction_delete(transaction); + } + + ASurfaceControl* surfaceControl = nullptr; +}; + +extern "C" JNIEXPORT jlong JNICALL +Java_com_android_test_hwui_FrontBufferedLayer_nCreate(JNIEnv* env, jclass, jobject jSurface) { + ANativeWindow* window = ANativeWindow_fromSurface(env, jSurface); + MyWrapper* wrapper = new MyWrapper(window); + ANativeWindow_release(window); + return reinterpret_cast<jlong>(wrapper); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_android_test_hwui_FrontBufferedLayer_nDestroy(JNIEnv*, jclass, jlong ptr) { + MyWrapper* wrapper = reinterpret_cast<MyWrapper*>(ptr); + delete wrapper; +} + +extern "C" JNIEXPORT void JNICALL Java_com_android_test_hwui_FrontBufferedLayer_nUpdateBuffer( + JNIEnv* env, jclass, jlong ptr, jobject jbuffer) { + MyWrapper* wrapper = reinterpret_cast<MyWrapper*>(ptr); + AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, jbuffer); + wrapper->setBuffer(buffer); +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png b/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png Binary files differnew file mode 100644 index 000000000000..cc8adf15f4f0 --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg b/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg Binary files differnew file mode 100644 index 000000000000..b5aff104207a --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg diff --git a/tests/HwAccelerationTest/res/layout/pen_stylus.xml b/tests/HwAccelerationTest/res/layout/pen_stylus.xml new file mode 100644 index 000000000000..37aafed208fb --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/pen_stylus.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.test.hwui.FrontBufferedLayer + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" +/>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml b/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml new file mode 100644 index 000000000000..b91377d1ab49 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml @@ -0,0 +1,205 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout + android:id="@+id/TopLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="8dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Sample Card #1"/> + + <androidx.cardview.widget.CardView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:layout_marginBottom="16dp" + android:clickable="true" + android:focusable="true" + android:minHeight="148dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:paddingBottom="8dp"> + + <ImageView + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:contentDescription="Logo" + android:src="@drawable/icon"/> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1.0" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:orientation="vertical"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:text="Image Transition"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ellipsize="end" + android:maxLines="1" + android:text="Touch the image to trigger the animation"/> + </LinearLayout> + </LinearLayout> + + <com.android.test.hwui.BitmapTransitionView + android:layout_width="match_parent" + android:layout_height="194dp" + android:padding="8dp"/> + + </LinearLayout> + + </androidx.cardview.widget.CardView> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Sample Card #2"/> + + <androidx.cardview.widget.CardView + android:id="@+id/CardView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:layout_marginBottom="16dp" + android:clickable="true" + android:focusable="true" + android:minHeight="148dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:paddingBottom="8dp"> + + <ImageView + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:contentDescription="Logo" + android:src="@drawable/icon"/> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1.0" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:orientation="vertical"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:text="View Group Manipulation"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ellipsize="end" + android:maxLines="1" + android:text="Tap the card to trigger the animation"/> + </LinearLayout> + </LinearLayout> + + <ImageView + android:layout_width="match_parent" + android:layout_height="194dp" + android:background="@android:color/transparent" + android:src="@drawable/weather_2"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:paddingBottom="8dp" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <RatingBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="Card Rating" + android:isIndicator="true" + android:numStars="5" + android:rating="4.5" + + android:stepSize="0.5"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:text="Category 4.5 Storm"/> + </LinearLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:maxLines="3" + android:textIsSelectable="true" + android:text="Lorem ipsum dolor sit amet, nec no nominavi scaevola. Per et + sint sapientem, nobis perpetua salutandi mei te. Quo tamquam probatus + reprehendunt in. Eos esse purto eruditi ea. Enim tation persius ut sea, + eos ad consul populo. Ne eum solet altera. Cibo eligendi et est, electram + theophrastus te vel eu."/> + + </LinearLayout> + + </LinearLayout> + + </androidx.cardview.widget.CardView> + </LinearLayout> +</ScrollView> diff --git a/tests/HwAccelerationTest/res/values/styles.xml b/tests/HwAccelerationTest/res/values/styles.xml index fa5437f38ace..55f4dd697907 100644 --- a/tests/HwAccelerationTest/res/values/styles.xml +++ b/tests/HwAccelerationTest/res/values/styles.xml @@ -41,4 +41,5 @@ <item name="android:spotShadowAlpha">1</item> --> </style> + </resources> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt new file mode 100644 index 000000000000..3af54503d469 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt @@ -0,0 +1,138 @@ +/* + * 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.test.hwui + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.BitmapShader +import android.graphics.Canvas +import android.graphics.ImageDecoder +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.RuntimeShader +import android.graphics.Shader +import android.util.AttributeSet +import android.view.View + +class BitmapTransitionView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private val mPaint = Paint() + private val mImageA = ImageDecoder.decodeBitmap( + ImageDecoder.createSource(context.resources, R.drawable.large_photo)) + private val mImageB = ImageDecoder.decodeBitmap( + ImageDecoder.createSource(context.resources, R.drawable.very_large_photo)) + private val mShaderA = BitmapShader(mImageA, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + private val mShaderB = BitmapShader(mImageB, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + private val mShader = RuntimeShader(AGSL) + private var mCurrentProgress = -1f + private var mForwardProgress = true + private var mCurrentAnimator = ValueAnimator.ofFloat(-1f, 1f) + + init { + isClickable = true + + mCurrentAnimator.duration = 1500 + mCurrentAnimator.addUpdateListener { animation -> + mCurrentProgress = animation.animatedValue as Float + postInvalidate() + } + } + + override fun performClick(): Boolean { + if (super.performClick()) return true + + if (mCurrentAnimator.isRunning) { + mCurrentAnimator.reverse() + return true + } + + if (mForwardProgress) { + mCurrentAnimator.setFloatValues(-1f, 1f) + mForwardProgress = false + } else { + mCurrentAnimator.setFloatValues(1f, -1f) + mForwardProgress = true + } + + mCurrentAnimator.start() + postInvalidate() + return true + } + + override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { + val matrixA = Matrix() + val matrixB = Matrix() + + matrixA.postScale(width.toFloat() / mImageA.width, height.toFloat() / mImageA.height) + matrixB.postScale(width.toFloat() / mImageB.width, height.toFloat() / mImageB.height) + + mShaderA.setLocalMatrix(matrixA) + mShaderB.setLocalMatrix(matrixB) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + mShader.setInputShader("imageA", mShaderA) + mShader.setInputShader("imageB", mShaderB) + mShader.setIntUniform("imageDimensions", width, height) + mShader.setFloatUniform("progress", mCurrentProgress) + + mPaint.shader = mShader + canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint) + } + + private companion object { + const val AGSL = """ + uniform shader imageA; + uniform shader imageB; + uniform ivec2 imageDimensions; + uniform float progress; + + const vec2 iSize = vec2(48.0, 48.0); + const float iDir = 0.5; + const float iRand = 0.81; + + float hash12(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * .1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); + } + + float ramp(float2 p) { + return mix(hash12(p), + dot(p/vec2(imageDimensions), float2(iDir, 1 - iDir)), + iRand); + } + + half4 main(float2 p) { + float2 lowRes = p / iSize; + float2 cellCenter = (floor(lowRes) + 0.5) * iSize; + float2 posInCell = fract(lowRes) * 2 - 1; + + float v = ramp(cellCenter) + progress; + float distToCenter = max(abs(posInCell.x), abs(posInCell.y)); + + return distToCenter > v ? imageA.eval(p).rgb1 : imageB.eval(p).rgb1; + } + """ + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java index c06f8fd44c03..fafe60b46676 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java @@ -63,7 +63,7 @@ public class ColorFiltersMutateActivity extends Activity { "uniform shader bitmapShader;\n" + "uniform float param1;\n" + "half4 main(float2 xy) {\n" - + " return half4(sample(bitmapShader, xy).rgb, param1);\n" + + " return half4(bitmapShader.eval(xy).rgb, param1);\n" + "}\n"; BitmapsView(Context c) { @@ -83,8 +83,8 @@ public class ColorFiltersMutateActivity extends Activity { mBlendPaint = new Paint(); mBlendPaint.setColorFilter(new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_OVER)); - mRuntimeShader = new RuntimeShader(sSkSL, false); - mRuntimeShader.setUniform("param1", mShaderParam1); + mRuntimeShader = new RuntimeShader(sSkSL); + mRuntimeShader.setFloatUniform("param1", mShaderParam1); mRuntimeShader.setInputShader("bitmapShader", new BitmapShader(mBitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); @@ -177,7 +177,7 @@ public class ColorFiltersMutateActivity extends Activity { public void setShaderParam1(float value) { mShaderParam1 = value; - mRuntimeShader.setUniform("param1", mShaderParam1); + mRuntimeShader.setFloatUniform("param1", mShaderParam1); invalidate(); } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java index 7ea2a62d7494..d4bc2a6d3317 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java @@ -42,7 +42,7 @@ public class ColoredRectsActivity extends Activity { swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); frame.addView(swView); final RectsView hwBothView = new RectsView(this, 850, Color.GREEN); - // Don't actually need to render to a hw layer, but it's a good sanity-check that + // Don't actually need to render to a hw layer, but it's a good check that // we're rendering to/from layers correctly hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null); frame.addView(hwBothView); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/FrontBufferedLayer.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/FrontBufferedLayer.kt new file mode 100644 index 000000000000..ebec22e29d69 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/FrontBufferedLayer.kt @@ -0,0 +1,132 @@ +/* + * 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.test.hwui + +import android.content.Context +import android.graphics.BlendMode +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.util.AttributeSet +import android.view.InputDevice +import android.view.MotionEvent +import android.view.Surface +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.view.WindowInsets +import android.view.WindowInsetsController + +class FrontBufferedLayer : SurfaceView, SurfaceHolder.Callback { + var mRenderer: PenStylusActivity.SingleBufferedCanvas? = null + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + init { + holder.addCallback(this) + setZOrderOnTop(true) + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + nDestroy(mNativePtr) + mNativePtr = nCreate(holder.surface) + mRenderer = PenStylusActivity.SingleBufferedCanvas(width, height) + clearOverlay() + + if ((false)) { + val canvas = holder.lockCanvas() + canvas.drawColor(Color.LTGRAY) + holder.unlockCanvasAndPost(canvas) + } + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + mRenderer = null + nDestroy(mNativePtr) + mNativePtr = 0 + } + + override fun surfaceCreated(holder: SurfaceHolder) { + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + requestUnbufferedDispatch(InputDevice.SOURCE_CLASS_POINTER) + this.windowInsetsController?.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT) + this.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) + this.windowInsetsController?.hide(WindowInsets.Type.statusBars()) + } + + private fun clearOverlay() { + mRenderer?.let { + it.update(null) { + drawColor(Color.WHITE, BlendMode.SRC) + } + nUpdateBuffer(mNativePtr, it.mHardwareBuffer) + } + } + + private var prevX: Float = 0f + private var prevY: Float = 0f + private val paint = Paint().also { + it.color = Color.BLACK + it.strokeWidth = 10f + it.isAntiAlias = true + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!event.isFromSource(InputDevice.SOURCE_STYLUS)) { + if (event.action == MotionEvent.ACTION_DOWN) { + clearOverlay() + } + return true + } + val action = event.actionMasked + if (action == MotionEvent.ACTION_DOWN || + action == MotionEvent.ACTION_MOVE) { + mRenderer?.let { + val left = minOf(prevX, event.x).toInt() - 10 + val top = minOf(prevY, event.y).toInt() - 10 + val right = maxOf(prevX, event.x).toInt() + 10 + val bottom = maxOf(prevY, event.y).toInt() + 10 + it.update(Rect(left, top, right, bottom)) { + if (action == MotionEvent.ACTION_MOVE) { + drawLine(prevX, prevY, event.x, event.y, paint) + } + drawCircle(event.x, event.y, 5f, paint) + } + nUpdateBuffer(mNativePtr, it.mHardwareBuffer) + } + prevX = event.x + prevY = event.y + } + return true + } + + private var mNativePtr: Long = 0 + + private external fun nCreate(surface: Surface): Long + private external fun nDestroy(ptr: Long) + private external fun nUpdateBuffer(ptr: Long, buffer: HardwareBuffer) + + companion object { + init { + System.loadLibrary("hwaccelerationtest_jni") + } + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java index 7173a85f73e7..584ab596836c 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java @@ -42,7 +42,7 @@ public class Lines2Activity extends Activity { swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); frame.addView(swView); final LinesView hwBothView = new LinesView(this, 850, Color.GREEN); - // Don't actually need to render to a hw layer, but it's a good sanity-check that + // Don't actually need to render to a hw layer, but it's a good check that // we're rendering to/from layers correctly hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null); frame.addView(hwBothView); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PenStylusActivity.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/PenStylusActivity.kt new file mode 100644 index 000000000000..1445b1db801e --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PenStylusActivity.kt @@ -0,0 +1,85 @@ +/* + * 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.test.hwui + +import android.app.Activity +import android.os.Bundle +import android.hardware.HardwareBuffer +import android.graphics.Canvas +import android.graphics.PixelFormat +import android.graphics.Rect +import android.media.ImageReader +import android.view.Surface +import java.lang.IllegalArgumentException + +const val USAGE_HWC = 0x800L + +class PenStylusActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(FrontBufferedLayer(this)) + } + + class SingleBufferedCanvas : AutoCloseable { + val mHardwareBuffer: HardwareBuffer + private var mCanvas: Canvas? + private val mImageReader: ImageReader + private val mSurface: Surface + + constructor(width: Int, height: Int) { + mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_CPU_READ_RARELY or HardwareBuffer.USAGE_CPU_WRITE_RARELY or + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or USAGE_HWC) + + mSurface = mImageReader.surface + mSurface.unlockCanvasAndPost(mSurface.lockCanvas(null)) + val image = mImageReader.acquireNextImage() + mHardwareBuffer = image.hardwareBuffer!! + image.close() + mCanvas = mSurface.lockCanvas(null) + } + + fun lockCanvas(rect: Rect?): Canvas { + if (mCanvas != null) { + unlockCanvas(mCanvas!!) + } + mCanvas = mSurface.lockCanvas(rect) + return mCanvas!! + } + + fun unlockCanvas(canvas: Canvas) { + if (this.mCanvas !== canvas) throw IllegalArgumentException() + mSurface.unlockCanvasAndPost(canvas) + this.mCanvas = null + mImageReader.acquireNextImage().close() + } + + inline fun update(area: Rect?, func: Canvas.() -> Unit) { + val canvas = lockCanvas(area) + func(canvas) + unlockCanvas(canvas) + } + + override fun close() { + mHardwareBuffer.close() + mSurface.unlockCanvasAndPost(mCanvas) + mImageReader.close() + } + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt new file mode 100644 index 000000000000..3c71b96c6c31 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt @@ -0,0 +1,109 @@ +/* + * 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.test.hwui + +import android.animation.ValueAnimator +import android.app.Activity +import android.graphics.Bitmap +import android.graphics.BitmapShader +import android.graphics.ImageDecoder +import android.graphics.Matrix +import android.graphics.Shader +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.os.Bundle +import android.view.View + +class RenderEffectViewActivity : Activity() { + + private val mDropsShader = RuntimeShader(dropsAGSL) + private var mDropsAnimator = ValueAnimator.ofFloat(0f, 1f) + private var mStartTime = System.currentTimeMillis() + private lateinit var mScratchesImage: Bitmap + private lateinit var mScratchesShader: BitmapShader + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.view_runtime_shader) + + val dropsView = findViewById<View>(R.id.CardView)!! + dropsView.isClickable = true + dropsView.setOnClickListener { + if (mDropsAnimator.isRunning) { + mDropsAnimator.cancel() + dropsView.setRenderEffect(null) + } else { + mDropsAnimator.start() + } + } + + val imgSource = ImageDecoder.createSource(resources, R.drawable.scratches) + mScratchesImage = ImageDecoder.decodeBitmap(imgSource) + mScratchesShader = BitmapShader(mScratchesImage, + Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + + mDropsAnimator.duration = 1000 + mDropsAnimator.repeatCount = ValueAnimator.INFINITE + mDropsAnimator.addUpdateListener { _ -> + val viewWidth = dropsView.width.toFloat() + val viewHeight = dropsView.height.toFloat() + val scratchesMatrix = Matrix() + scratchesMatrix.postScale(viewWidth / mScratchesImage.width, + viewHeight / mScratchesImage.height) + mScratchesShader.setLocalMatrix(scratchesMatrix) + + mDropsShader.setInputShader("scratches", mScratchesShader) + mDropsShader.setFloatUniform("elapsedSeconds", + (System.currentTimeMillis() - mStartTime) / 1000f) + mDropsShader.setFloatUniform("viewDimensions", viewWidth, viewHeight) + + val dropsEffect = RenderEffect.createRuntimeShaderEffect(mDropsShader, "background") + val blurEffect = RenderEffect.createBlurEffect(10f, 10f, Shader.TileMode.CLAMP) + + dropsView.setRenderEffect(RenderEffect.createChainEffect(dropsEffect, blurEffect)) + } + } + + private companion object { + const val dropsAGSL = """ + uniform float elapsedSeconds; + uniform vec2 viewDimensions; + uniform shader background; + uniform shader scratches; + + vec2 dropsUV(vec2 fragCoord ) { + vec2 uv = fragCoord.xy / viewDimensions.xy; // 0 <> 1 + vec2 offs = vec2(0.); + return (offs + uv).xy; + } + + const vec3 iFrostColorRGB = vec3(0.5, 0.5, 0.5); + const float iFrostColorAlpha = .3; + + half4 main(float2 fragCoord) { + half4 bg = background.eval(dropsUV(fragCoord)*viewDimensions.xy); + float2 scratchCoord = fragCoord.xy / viewDimensions.xy;; + scratchCoord += 1.5; + scratchCoord = mod(scratchCoord, 1); + half scratch = scratches.eval(scratchCoord*viewDimensions.xy).r; + bg.rgb = mix(bg.rgb, iFrostColorRGB, iFrostColorAlpha); + bg.rgb = mix(bg.rgb, half3(1), pow(scratch,3)); + return bg; + } + """ + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java index d925541c76d6..b78907c46744 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java @@ -91,7 +91,7 @@ public class RippleActivity extends Activity { + " d = rand(float2(x, y)) > density ? d : d * .2;\n" + " d = d * rand(float2(fraction, x * y));\n" + " float alpha = 1. - pow(fraction, 3.);\n" - + " return float4(sample(in_paintColor, p).rgb, d * alpha);\n" + + " return float4(in_paintColor.eval(p).rgb, d * alpha);\n" + "}"; RippleView(Context c) { @@ -109,8 +109,8 @@ public class RippleActivity extends Activity { p.setColor(mColor); mPaint = CanvasProperty.createPaint(p); - mRuntimeShader = new RuntimeShader(sSkSL, false); - mRuntimeShader.setUniform("in_maxRadius", MAX_RADIUS); + mRuntimeShader = new RuntimeShader(sSkSL); + mRuntimeShader.setFloatUniform("in_maxRadius", MAX_RADIUS); } @Override diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java index 3307c36d9d1a..2990c9e59fec 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java @@ -69,7 +69,7 @@ public class StretchShaderActivity extends Activity { linearLayout.setOrientation(LinearLayout.VERTICAL); mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap(); - mRuntimeShader = new RuntimeShader(SKSL, false); + mRuntimeShader = new RuntimeShader(SKSL); BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); @@ -357,18 +357,18 @@ public class StretchShaderActivity extends Activity { float uScrollX = mScrollX; float uScrollY = mScrollY; - mRuntimeShader.setUniform("uMaxStretchIntensity", mMaxStretchIntensity); - mRuntimeShader.setUniform("uStretchAffectedDist", mStretchAffectedDistance); - mRuntimeShader.setUniform("uDistanceStretchedX", distanceStretchedX); - mRuntimeShader.setUniform("uDistanceStretchedY", distanceStretchedY); - mRuntimeShader.setUniform("uDistDiffX", diffX); - mRuntimeShader.setUniform("uDistDiffY", diffY); - mRuntimeShader.setUniform("uOverscrollX", normOverScrollDistX); - mRuntimeShader.setUniform("uOverscrollY", normOverScrollDistY); - mRuntimeShader.setUniform("uScrollX", uScrollX); - mRuntimeShader.setUniform("uScrollY", uScrollY); - mRuntimeShader.setUniform("viewportWidth", width); - mRuntimeShader.setUniform("viewportHeight", height); + mRuntimeShader.setFloatUniform("uMaxStretchIntensity", mMaxStretchIntensity); + mRuntimeShader.setFloatUniform("uStretchAffectedDist", mStretchAffectedDistance); + mRuntimeShader.setFloatUniform("uDistanceStretchedX", distanceStretchedX); + mRuntimeShader.setFloatUniform("uDistanceStretchedY", distanceStretchedY); + mRuntimeShader.setFloatUniform("uDistDiffX", diffX); + mRuntimeShader.setFloatUniform("uDistDiffY", diffY); + mRuntimeShader.setFloatUniform("uOverscrollX", normOverScrollDistX); + mRuntimeShader.setFloatUniform("uOverscrollY", normOverScrollDistY); + mRuntimeShader.setFloatUniform("uScrollX", uScrollX); + mRuntimeShader.setFloatUniform("uScrollY", uScrollY); + mRuntimeShader.setFloatUniform("viewportWidth", width); + mRuntimeShader.setFloatUniform("viewportHeight", height); mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); @@ -532,6 +532,6 @@ public class StretchShaderActivity extends Activity { + " uv.y = outV;\n" + " coord.x = uv.x * viewportWidth;\n" + " coord.y = uv.y * viewportHeight;\n" - + " return sample(uContentTexture, coord);\n" + + " return uContentTexture.eval(coord);\n" + "}"; } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index eacf5b287a2e..de9bbb6ef9fa 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -18,6 +18,8 @@ android_test { static_libs: [ "androidx.test.ext.junit", "androidx.test.rules", + "services.core.unboosted", + "testables", "truth-prebuilt", "ub-uiautomator", ], diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 4da3eca25ea0..1d65cc35c3bc 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -19,7 +19,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.filters.MediumTest +import android.app.ActivityManager +import android.app.ApplicationExitInfo import android.graphics.Rect +import android.os.Build +import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.SystemClock import android.provider.Settings import android.provider.Settings.Global.HIDE_ERROR_DIALOGS @@ -27,10 +31,13 @@ import android.support.test.uiautomator.By import android.support.test.uiautomator.UiDevice import android.support.test.uiautomator.UiObject2 import android.support.test.uiautomator.Until +import android.testing.PollingCheck import android.view.InputDevice import android.view.MotionEvent import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before import org.junit.Test @@ -51,28 +58,99 @@ import org.junit.runner.RunWith class AnrTest { companion object { private const val TAG = "AnrTest" + private const val ALL_PIDS = 0 + private const val NO_MAX = 0 } - val mInstrumentation = InstrumentationRegistry.getInstrumentation() - var mHideErrorDialogs = 0 + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private var hideErrorDialogs = 0 + private lateinit var PACKAGE_NAME: String + private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * + Build.HW_TIMEOUT_MULTIPLIER) @Before fun setUp() { - val contentResolver = mInstrumentation.targetContext.contentResolver - mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + val contentResolver = instrumentation.targetContext.contentResolver + hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName() } @After fun tearDown() { - val contentResolver = mInstrumentation.targetContext.contentResolver - Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs) + val contentResolver = instrumentation.targetContext.contentResolver + Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs) } @Test - fun testGestureMonitorAnr() { + fun testGestureMonitorAnr_Close() { + triggerAnr() + clickCloseAppOnAnrDialog() + } + + @Test + fun testGestureMonitorAnr_Wait() { + triggerAnr() + clickWaitOnAnrDialog() + SystemClock.sleep(500) // Wait at least 500ms after tapping on wait + // ANR dialog should reappear after a delay - find the close button on it to verify + clickCloseAppOnAnrDialog() + } + + private fun clickCloseAppOnAnrDialog() { + // Find anr dialog and kill app + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) + val closeAppButton: UiObject2? = + uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000) + if (closeAppButton == null) { + fail("Could not find anr dialog") + return + } + val initialReasons = getExitReasons() + closeAppButton.click() + /** + * We must wait for the app to be fully closed before exiting this test. This is because + * another test may again invoke 'am start' for the same activity. + * If the 1st process that got ANRd isn't killed by the time second 'am start' runs, + * the killing logic will apply to the newly launched 'am start' instance, and the second + * test will fail because the unresponsive activity will never be launched. + */ + waitForNewExitReason(initialReasons[0].timestamp) + } + + private fun clickWaitOnAnrDialog() { + // Find anr dialog and tap on wait + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) + val waitButton: UiObject2? = + uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000) + if (waitButton == null) { + fail("Could not find anr dialog/wait button") + return + } + waitButton.click() + } + + private fun getExitReasons(): List<ApplicationExitInfo> { + lateinit var infos: List<ApplicationExitInfo> + instrumentation.runOnMainSync { + val am = instrumentation.getContext().getSystemService(ActivityManager::class.java) + infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX) + } + return infos + } + + private fun waitForNewExitReason(previousExitTimestamp: Long) { + PollingCheck.waitFor { + getExitReasons()[0].timestamp > previousExitTimestamp + } + val reasons = getExitReasons() + assertTrue(reasons[0].timestamp > previousExitTimestamp) + assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason) + } + + private fun triggerAnr() { startUnresponsiveActivity() - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val obj: UiObject2? = uiDevice.wait(Until.findObject( By.text("Unresponsive gesture monitor")), 10000) @@ -87,29 +165,14 @@ class AnrTest { MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */) downEvent.source = InputDevice.SOURCE_TOUCHSCREEN - mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) - - // Todo: replace using timeout from android.hardware.input.IInputManager - SystemClock.sleep(5000) // default ANR timeout for gesture monitors - - clickCloseAppOnAnrDialog() - } + instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) - private fun clickCloseAppOnAnrDialog() { - // Find anr dialog and kill app - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) - val closeAppButton: UiObject2? = - uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000) - if (closeAppButton == null) { - fail("Could not find anr dialog") - return - } - closeAppButton.click() + SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors } private fun startUnresponsiveActivity() { val flags = " -W -n " - val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity" - mInstrumentation.uiAutomation.executeShellCommand(startCmd) + val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity" + instrumentation.uiAutomation.executeShellCommand(startCmd) } -}
\ No newline at end of file +} diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt index 014efc2b954c..37b67f4c183c 100644 --- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt @@ -17,16 +17,11 @@ package com.android.test.input import android.os.HandlerThread -import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.Looper import android.view.InputChannel import android.view.InputEvent import android.view.InputEventReceiver -import android.view.InputEventSender import android.view.KeyEvent -import android.view.MotionEvent -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.TimeUnit import org.junit.Assert.assertEquals import org.junit.After import org.junit.Before @@ -46,54 +41,19 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) { assertEquals(expected.displayId, received.displayId) } -private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T { - try { - return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) - } catch (e: InterruptedException) { - throw RuntimeException("Unexpectedly interrupted while waiting for event") - } +private fun getTestKeyEvent(): KeyEvent { + return KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_A, 0 /*repeat*/) } -class TestInputEventReceiver(channel: InputChannel, looper: Looper) : +private class CrashingInputEventReceiver(channel: InputChannel, looper: Looper) : InputEventReceiver(channel, looper) { - private val mInputEvents = LinkedBlockingQueue<InputEvent>() - override fun onInputEvent(event: InputEvent) { - when (event) { - is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) - is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) - else -> throw Exception("Received $event is neither a key nor a motion") + try { + throw IllegalArgumentException("This receiver crashes when it receives input event") + } finally { + finishInputEvent(event, true /*handled*/) } - finishInputEvent(event, true /*handled*/) - } - - fun getInputEvent(): InputEvent { - return getEvent(mInputEvents) - } -} - -class TestInputEventSender(channel: InputChannel, looper: Looper) : - InputEventSender(channel, looper) { - data class FinishedSignal(val seq: Int, val handled: Boolean) - data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long) - - private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() - private val mTimelines = LinkedBlockingQueue<Timeline>() - - override fun onInputEventFinished(seq: Int, handled: Boolean) { - mFinishedSignals.put(FinishedSignal(seq, handled)) - } - - override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) { - mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime)) - } - - fun getFinishedSignal(): FinishedSignal { - return getEvent(mFinishedSignals) - } - - fun getTimeline(): Timeline { - return getEvent(mTimelines) } } @@ -102,8 +62,8 @@ class InputEventSenderAndReceiverTest { private const val TAG = "InputEventSenderAndReceiverTest" } private val mHandlerThread = HandlerThread("Process input events") - private lateinit var mReceiver: TestInputEventReceiver - private lateinit var mSender: TestInputEventSender + private lateinit var mReceiver: SpyInputEventReceiver + private lateinit var mSender: SpyInputEventSender @Before fun setUp() { @@ -111,8 +71,8 @@ class InputEventSenderAndReceiverTest { mHandlerThread.start() val looper = mHandlerThread.getLooper() - mSender = TestInputEventSender(channels[0], looper) - mReceiver = TestInputEventReceiver(channels[1], looper) + mSender = SpyInputEventSender(channels[0], looper) + mReceiver = SpyInputEventReceiver(channels[1], looper) } @After @@ -122,8 +82,7 @@ class InputEventSenderAndReceiverTest { @Test fun testSendAndReceiveKey() { - val key = KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_A, 0 /*repeat*/) + val key = getTestKeyEvent() val seq = 10 mSender.sendInputEvent(seq, key) val receivedKey = mReceiver.getInputEvent() as KeyEvent @@ -133,13 +92,13 @@ class InputEventSenderAndReceiverTest { assertKeyEvent(key, receivedKey) // Check sender - assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) + assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) } // The timeline case is slightly unusual because it goes from InputConsumer to InputPublisher. @Test fun testSendAndReceiveTimeline() { - val sent = TestInputEventSender.Timeline( + val sent = SpyInputEventSender.Timeline( inputEventId = 1, gpuCompletedTime = 2, presentTime = 3) mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime) val received = mSender.getTimeline() @@ -151,7 +110,7 @@ class InputEventSenderAndReceiverTest { // event processing. @Test fun testSendAndReceiveInvalidTimeline() { - val sent = TestInputEventSender.Timeline( + val sent = SpyInputEventSender.Timeline( inputEventId = 1, gpuCompletedTime = 3, presentTime = 2) mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime) val received = mSender.getTimeline() @@ -162,4 +121,41 @@ class InputEventSenderAndReceiverTest { val receivedSecondTimeline = mSender.getTimeline() assertEquals(null, receivedSecondTimeline) } + + /** + * If a receiver throws an exception during 'onInputEvent' execution, the 'finally' block still + * completes, and therefore, finishInputEvent is called. Make sure that there's no crash in the + * native layer in these circumstances. + * In this test, we are reusing the 'mHandlerThread', but we are creating new sender and + * receiver. + */ + @Test + fun testCrashingReceiverDoesNotCrash() { + val channels = InputChannel.openInputChannelPair("TestChannel2") + val sender = SpyInputEventSender(channels[0], mHandlerThread.getLooper()) + + // Need a separate thread for the receiver so that the sender can still get the response + // after the receiver crashes + val receiverThread = HandlerThread("Receive input events") + receiverThread.start() + val crashingReceiver = CrashingInputEventReceiver(channels[1], receiverThread.getLooper()) + receiverThread.setUncaughtExceptionHandler { thread, exception -> + if (thread == receiverThread && exception is IllegalArgumentException) { + // do nothing - this is the exception that we need to ignore + } else { + throw exception + } + } + + val key = getTestKeyEvent() + val seq = 11 + sender.sendInputEvent(seq, key) + val finishedSignal = sender.getFinishedSignal() + assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) + + // Clean up + crashingReceiver.dispose() + sender.dispose() + receiverThread.quitSafely() + } } diff --git a/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt b/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt new file mode 100644 index 000000000000..1099878a1954 --- /dev/null +++ b/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt @@ -0,0 +1,93 @@ +/* + * 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.test.input + +import android.os.HandlerThread +import android.view.InputChannel +import android.view.InputDevice +import android.view.MotionEvent +import android.view.WindowManagerPolicyConstants.PointerEventListener + +import com.android.server.UiThread +import com.android.server.wm.PointerEventDispatcher +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.After +import org.junit.Before +import org.junit.Test + +private class CrashingPointerEventListener : PointerEventListener { + override fun onPointerEvent(motionEvent: MotionEvent) { + throw IllegalArgumentException("This listener crashes when input event occurs") + } +} + +class PointerEventDispatcherTest { + companion object { + private const val TAG = "PointerEventDispatcherTest" + } + private val mHandlerThread = HandlerThread("Process input events") + private lateinit var mSender: SpyInputEventSender + private lateinit var mPointerEventDispatcher: PointerEventDispatcher + private val mListener = CrashingPointerEventListener() + + @Before + fun setUp() { + val channels = InputChannel.openInputChannelPair("TestChannel") + + mHandlerThread.start() + val looper = mHandlerThread.getLooper() + mSender = SpyInputEventSender(channels[0], looper) + + mPointerEventDispatcher = PointerEventDispatcher(channels[1]) + mPointerEventDispatcher.registerInputEventListener(mListener) + } + + @After + fun tearDown() { + mHandlerThread.quitSafely() + } + + @Test + fun testSendMotionToCrashingListenerDoesNotCrash() { + // The exception will occur on the UiThread, so we can't catch it here on the test thread + UiThread.get().setUncaughtExceptionHandler { thread, exception -> + if (thread == UiThread.get() && exception is IllegalArgumentException) { + // do nothing - this is the exception that we need to ignore + } else { + throw exception + } + } + + // The MotionEvent properties aren't important for this test, as long as the event + // is a pointer event, so that it gets processed by CrashingPointerEventListener + val downTime = 0L + val motionEvent = MotionEvent.obtain(downTime, downTime, + MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + val seq = 10 + mSender.sendInputEvent(seq, motionEvent) + val finishedSignal = mSender.getFinishedSignal() + + // Since the listener raises an exception during the event handling, the event should be + // marked as 'not handled'. + assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = false), finishedSignal) + // Ensure that there aren't double finish calls. This would crash if there's a call + // to finish twice. + assertNull(mSender.getFinishedSignal()) + } +} diff --git a/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt b/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt new file mode 100644 index 000000000000..2d9af9a65d33 --- /dev/null +++ b/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt @@ -0,0 +1,75 @@ +/* + * 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.test.input + +import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS +import android.os.Looper +import android.view.InputChannel +import android.view.InputEvent +import android.view.InputEventReceiver +import android.view.InputEventSender +import android.view.KeyEvent +import android.view.MotionEvent +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit + +private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T? { + return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) +} + +class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + private val mInputEvents = LinkedBlockingQueue<InputEvent>() + + override fun onInputEvent(event: InputEvent) { + when (event) { + is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) + is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) + else -> throw Exception("Received $event is neither a key nor a motion") + } + finishInputEvent(event, true /*handled*/) + } + + fun getInputEvent(): InputEvent? { + return getEvent(mInputEvents) + } +} + +class SpyInputEventSender(channel: InputChannel, looper: Looper) : + InputEventSender(channel, looper) { + data class FinishedSignal(val seq: Int, val handled: Boolean) + data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long) + + private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() + private val mTimelines = LinkedBlockingQueue<Timeline>() + + override fun onInputEventFinished(seq: Int, handled: Boolean) { + mFinishedSignals.put(FinishedSignal(seq, handled)) + } + + override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) { + mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime)) + } + + fun getFinishedSignal(): FinishedSignal? { + return getEvent(mFinishedSignals) + } + + fun getTimeline(): Timeline? { + return getEvent(mTimelines) + } +} diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp new file mode 100644 index 000000000000..0ad38768238a --- /dev/null +++ b/tests/InputMethodStressTest/Android.bp @@ -0,0 +1,36 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "InputMethodStressTest", + srcs: ["src/**/*.java"], + libs: ["android.test.runner"], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.uiautomator_uiautomator", + "compatibility-device-util-axt", + "platform-test-annotations", + "platform-test-rules", + "truth-prebuilt", + ], + test_suites: [ + "general-tests", + "vts", + ], + sdk_version: "31", +} diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml new file mode 100644 index 000000000000..f5fe8f2e8e76 --- /dev/null +++ b/tests/InputMethodStressTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.inputmethod.stresstest"> + + <application> + <activity android:name=".AutoShowTest$TestActivity"/> + <activity android:name=".ImeOpenCloseStressTest$TestActivity"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.inputmethod.stresstest"> + </instrumentation> +</manifest> diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml new file mode 100644 index 000000000000..9ac41351f684 --- /dev/null +++ b/tests/InputMethodStressTest/AndroidTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="InputMethod integration/regression test"> + <option name="test-suite-tag" value="apct" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> + <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="InputMethodStressTest.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.inputmethod.stresstest" /> + </test> + + <!-- Collect the files in the dump directory for debugging --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/sdcard/InputMethodStressTest/" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> +</configuration> diff --git a/tests/InputMethodStressTest/OWNERS b/tests/InputMethodStressTest/OWNERS new file mode 100644 index 000000000000..6bb4b17ed4eb --- /dev/null +++ b/tests/InputMethodStressTest/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 34867 + +include /services/core/java/com/android/server/inputmethod/OWNERS diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING new file mode 100644 index 000000000000..ad07205ab02d --- /dev/null +++ b/tests/InputMethodStressTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "InputMethodStressTest" + } + ] +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java new file mode 100644 index 000000000000..c84c2bcf19c6 --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java @@ -0,0 +1,92 @@ +/* + * 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.inputmethod.stresstest; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; + +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; +import android.platform.test.annotations.RootPermissionTest; +import android.platform.test.rule.UnlockScreenRule; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RootPermissionTest +@RunWith(AndroidJUnit4.class) +public final class AutoShowTest { + + @Rule + public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + + @Rule + public ScreenCaptureRule mScreenCaptureRule = + new ScreenCaptureRule("/sdcard/InputMethodStressTest"); + + @Test + public void autoShow() { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + Intent intent = new Intent() + .setAction(Intent.ACTION_MAIN) + .setClass(instrumentation.getContext(), TestActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent); + EditText editText = activity.getEditText(); + waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + waitOnMainUntilImeIsShown(editText); + } + + public static class TestActivity extends Activity { + private EditText mEditText; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // IME will be auto-shown if the following conditions are met: + // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED. + // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE. + getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE); + LinearLayout rootView = new LinearLayout(this); + rootView.setOrientation(LinearLayout.VERTICAL); + mEditText = new EditText(this); + rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + setContentView(rootView); + // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true). + mEditText.requestFocus(); + } + + public EditText getEditText() { + return mEditText; + } + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java new file mode 100644 index 000000000000..8419276f4406 --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java @@ -0,0 +1,239 @@ +/* + * 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.inputmethod.stresstest; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; + +import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; +import android.os.SystemClock; +import android.platform.test.annotations.RootPermissionTest; +import android.platform.test.rule.UnlockScreenRule; +import android.util.Log; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +@RootPermissionTest +@RunWith(AndroidJUnit4.class) +public final class ImeOpenCloseStressTest { + + private static final String TAG = "ImeOpenCloseStressTest"; + private static final int NUM_TEST_ITERATIONS = 10; + + @Rule + public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + + @Rule + public ScreenCaptureRule mScreenCaptureRule = + new ScreenCaptureRule("/sdcard/InputMethodStressTest"); + private Instrumentation mInstrumentation; + + @Before + public void setUp() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + @Test + public void testShowHide_waitingVisibilityChange() { + TestActivity activity = TestActivity.start(); + EditText editText = activity.getEditText(); + waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { + String msgPrefix = "Iteration #" + i + " "; + Log.i(TAG, msgPrefix + "start"); + mInstrumentation.runOnMainSync(activity::showIme); + waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText)); + mInstrumentation.runOnMainSync(activity::hideIme); + waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText)); + } + } + + @Test + public void testShowHide_waitingAnimationEnd() { + TestActivity activity = TestActivity.start(); + activity.enableAnimationMonitoring(); + EditText editText = activity.getEditText(); + waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { + String msgPrefix = "Iteration #" + i + " "; + Log.i(TAG, msgPrefix + "start"); + mInstrumentation.runOnMainSync(activity::showIme); + waitOnMainUntil(msgPrefix + "IME should be visible", + () -> !activity.isAnimating() && isImeShown(editText)); + mInstrumentation.runOnMainSync(activity::hideIme); + waitOnMainUntil(msgPrefix + "IME should be hidden", + () -> !activity.isAnimating() && !isImeShown(editText)); + } + } + + @Test + public void testShowHide_intervalAfterHide() { + // Regression test for b/221483132 + TestActivity activity = TestActivity.start(); + EditText editText = activity.getEditText(); + // Intervals = 10, 20, 30, ..., 100, 150, 200, ... + List<Integer> intervals = new ArrayList<>(); + for (int i = 10; i < 100; i += 10) intervals.add(i); + for (int i = 100; i < 1000; i += 50) intervals.add(i); + waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + for (int intervalMillis : intervals) { + String msgPrefix = "Interval = " + intervalMillis + " "; + Log.i(TAG, msgPrefix + " start"); + mInstrumentation.runOnMainSync(activity::hideIme); + SystemClock.sleep(intervalMillis); + mInstrumentation.runOnMainSync(activity::showIme); + waitOnMainUntil(msgPrefix + "IME should be visible", + () -> isImeShown(editText)); + } + } + + @Test + public void testShowHideInSameFrame() { + TestActivity activity = TestActivity.start(); + activity.enableAnimationMonitoring(); + EditText editText = activity.getEditText(); + waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + + // hidden -> show -> hide + mInstrumentation.runOnMainSync(() -> { + Log.i(TAG, "Calling showIme() and hideIme()"); + activity.showIme(); + activity.hideIme(); + }); + // Wait until IMMS / IMS handles messages. + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText)); + + mInstrumentation.runOnMainSync(activity::showIme); + waitOnMainUntil("IME should be visible", + () -> !activity.isAnimating() && isImeShown(editText)); + mInstrumentation.waitForIdleSync(); + + // shown -> hide -> show + mInstrumentation.runOnMainSync(() -> { + Log.i(TAG, "Calling hideIme() and showIme()"); + activity.hideIme(); + activity.showIme(); + }); + // Wait until IMMS / IMS handles messages. + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + waitOnMainUntil("IME should be visible after hide/show", + () -> !activity.isAnimating() && isImeShown(editText)); + } + + public static class TestActivity extends Activity { + + private EditText mEditText; + private boolean mIsAnimating; + + private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = + new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + @Override + public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation, + WindowInsetsAnimation.Bounds bounds) { + mIsAnimating = true; + return super.onStart(animation, bounds); + } + + @Override + public void onEnd(WindowInsetsAnimation animation) { + super.onEnd(animation); + mIsAnimating = false; + } + + @Override + public WindowInsets onProgress(WindowInsets insets, + List<WindowInsetsAnimation> runningAnimations) { + return insets; + } + }; + + public static TestActivity start() { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + Intent intent = new Intent() + .setAction(Intent.ACTION_MAIN) + .setClass(instrumentation.getContext(), TestActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return (TestActivity) instrumentation.startActivitySync(intent); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LinearLayout rootView = new LinearLayout(this); + rootView.setOrientation(LinearLayout.VERTICAL); + mEditText = new EditText(this); + rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + setContentView(rootView); + } + + public EditText getEditText() { + return mEditText; + } + + public void showIme() { + Log.i(TAG, "TestActivity.showIme"); + mEditText.requestFocus(); + InputMethodManager imm = getSystemService(InputMethodManager.class); + imm.showSoftInput(mEditText, 0); + } + + public void hideIme() { + Log.i(TAG, "TestActivity.hideIme"); + InputMethodManager imm = getSystemService(InputMethodManager.class); + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + } + + public void enableAnimationMonitoring() { + // Enable WindowInsetsAnimation. + // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner. + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + getWindow().setDecorFitsSystemWindows(false); + mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); + }); + } + + public boolean isAnimating() { + return mIsAnimating; + } + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java new file mode 100644 index 000000000000..ba2ba3c75bc2 --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.stresstest; + +import static com.android.compatibility.common.util.SystemUtil.eventually; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.view.View; +import android.view.WindowInsets; + +import androidx.test.platform.app.InstrumentationRegistry; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** Utility methods for IME stress test. */ +public final class ImeStressTestUtil { + + private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); + + private ImeStressTestUtil() { + } + + /** Checks if the IME is shown on the window that the given view belongs to. */ + public static boolean isImeShown(View view) { + WindowInsets insets = view.getRootWindowInsets(); + return insets.isVisible(WindowInsets.Type.ime()); + } + + /** Calls the callable on the main thread and returns the result. */ + public static <V> V callOnMainSync(Callable<V> callable) { + AtomicReference<V> result = new AtomicReference<>(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + try { + result.set(callable.call()); + } catch (Exception e) { + throw new RuntimeException("Exception was thrown", e); + } + }); + return result.get(); + } + + /** + * Waits until {@code pred} returns true, or throws on timeout. + * + * <p>The given {@code pred} will be called on the main thread. + */ + public static void waitOnMainUntil(String message, Callable<Boolean> pred) { + eventually(() -> assertWithMessage(message).that(pred.call()).isTrue(), TIMEOUT); + } + + /** Waits until IME is shown, or throws on timeout. */ + public static void waitOnMainUntilImeIsShown(View view) { + eventually(() -> assertWithMessage("IME should be shown").that( + callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT); + } + + /** Waits until IME is hidden, or throws on timeout. */ + public static void waitOnMainUntilImeIsHidden(View view) { + //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT); + eventually(() -> assertWithMessage("IME should be hidden").that( + callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT); + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java new file mode 100644 index 000000000000..47f87d6d75ff --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java @@ -0,0 +1,160 @@ +/* + * 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.inputmethod.stresstest; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeFalse; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Icon; +import android.platform.test.annotations.RootPermissionTest; +import android.platform.test.rule.UnlockScreenRule; +import android.provider.Settings; +import android.view.KeyEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +@RootPermissionTest +@RunWith(AndroidJUnit4.class) +public final class NotificationTest { + + private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); + + private static final String CHANNEL_ID = "TEST_CHANNEL"; + private static final String CHANNEL_NAME = "Test channel"; + + private static final String REPLY_INPUT_KEY = "REPLY_KEY"; + private static final String REPLY_INPUT_LABEL = "Test reply label"; + private static final String ACTION_REPLY = "com.android.inputmethod.stresstest.ACTION_REPLY"; + private static final String REPLY_ACTION_LABEL = "Test reply"; + private static final int REPLY_REQUEST_CODE = 1; + + private static final String NOTIFICATION_TITLE = "Test notification"; + private static final String NOTIFICATION_CONTENT = "Test notification content"; + private static final int NOTIFICATION_ID = 2000; + + // This is for AOSP System UI for phones. When testing customized System UI, please modify here. + private static final BySelector REPLY_SEND_BUTTON_SELECTOR = + By.res("com.android.systemui", "remote_input_send"); + + @Rule + public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + + @Rule + public ScreenCaptureRule mScreenCaptureRule = + new ScreenCaptureRule("/sdcard/InputMethodStressTest"); + + private Context mContext; + private NotificationManager mNotificationManager; + private UiDevice mUiDevice; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + mNotificationManager = mContext.getSystemService(NotificationManager.class); + PackageManager pm = mContext.getPackageManager(); + // Do not run on Automotive. + assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); + // Do not run on TV. Direct Reply isn't supported on TV. + assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)); + } + + @After + public void tearDown() { + mNotificationManager.cancelAll(); + } + + @Test + public void testDirectReply() { + postMessagingNotification(); + mUiDevice.openNotification(); + // The text can be shown as-is, or all-caps, depending on the system. + Pattern actionLabelPattern = Pattern.compile(REPLY_ACTION_LABEL, Pattern.CASE_INSENSITIVE); + mUiDevice.wait(Until.findObject(By.text(actionLabelPattern)), TIMEOUT).click(); + // Verify that IME is visible. + assertThat(mUiDevice.wait(Until.findObject(By.pkg(getImePackage(mContext))), TIMEOUT)) + .isNotNull(); + // Type something, which enables the Send button, then click the Send button. + mUiDevice.pressKeyCode(KeyEvent.KEYCODE_A); + mUiDevice.pressKeyCode(KeyEvent.KEYCODE_B); + mUiDevice.pressKeyCode(KeyEvent.KEYCODE_C); + mUiDevice.wait(Until.findObject(REPLY_SEND_BUTTON_SELECTOR.enabled(true)), TIMEOUT).click(); + // Verify that IME is gone. + assertThat(mUiDevice.wait(Until.gone(By.pkg(getImePackage(mContext))), TIMEOUT)).isTrue(); + } + + private void postMessagingNotification() { + // Register the channel. It's safe to register the same channel again and again. + NotificationChannel channel = + new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_HIGH); + mNotificationManager.createNotificationChannel(channel); + + // Post inline reply notification. + PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, REPLY_REQUEST_CODE, new Intent().setAction(ACTION_REPLY), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + RemoteInput remoteInput = new RemoteInput.Builder(REPLY_INPUT_KEY) + .setLabel(REPLY_INPUT_LABEL) + .build(); + Icon icon = Icon.createWithResource(mContext, android.R.drawable.ic_menu_edit); + Notification.Action action = + new Notification.Action.Builder(icon, REPLY_ACTION_LABEL, pendingIntent) + .addRemoteInput(remoteInput) + .build(); + Notification notification = new Notification.Builder(mContext, CHANNEL_ID) + .setSmallIcon(android.R.drawable.ic_menu_edit) + .setContentTitle(NOTIFICATION_TITLE) + .setContentText(NOTIFICATION_CONTENT) + .addAction(action) + .build(); + mNotificationManager.notify(NOTIFICATION_ID, notification); + } + + private static String getImePackage(Context context) { + String imeId = Settings.Secure.getString( + context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + ComponentName cn = ComponentName.unflattenFromString(imeId); + assertThat(cn).isNotNull(); + return cn.getPackageName(); + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenCaptureRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenCaptureRule.java new file mode 100644 index 000000000000..4e4ef2edd06b --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenCaptureRule.java @@ -0,0 +1,69 @@ +/* + * 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.inputmethod.stresstest; + +import com.android.compatibility.common.util.SystemUtil; + +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Takes a screenshot when the test fails. + * + * <p>Use {@link com.android.tradefed.device.metric.FilePullerLogCollector} to collect screenshots + * taken. + * For example, in AndroidTest.xml: + * <code> + * <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + * <option name="directory-keys" value="/sdcard/MyTest/" /> + * <option name="collect-on-run-ended-only" value="true" /> + * </metrics_collector> + * </code> + * in MyTest.java: + * <code> + * @Rule + * public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/MyTest"); + * </code> + */ +public class ScreenCaptureRule extends TestWatcher { + + private static final String TAG = "ScreenCaptureRule"; + + private final String mOutDir; + + public ScreenCaptureRule(String outDir) { + mOutDir = outDir; + } + + @Override + protected void failed(Throwable e, Description description) { + super.failed(e, description); + String time = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()); + String fileName = "screenshot-" + time + ".png"; + capture(fileName); + } + + /** Take a screenshot. */ + public void capture(String fileName) { + SystemUtil.runCommandAndPrintOnLogcat(TAG, String.format("mkdir -p %s", mOutDir)); + SystemUtil.runCommandAndPrintOnLogcat(TAG, + String.format("screencap %s/%s", mOutDir, fileName)); + } +} diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java new file mode 100644 index 000000000000..65a3436a4c5e --- /dev/null +++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java @@ -0,0 +1,77 @@ +/* + * 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.internal.util; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.Parcelling.BuiltIn.ForInstant; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Instant; + +/** Tests for {@link Parcelling}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ParcellingTests { + + private Parcel mParcel = Parcel.obtain(); + + @Test + public void forInstant_normal() { + testForInstant(Instant.ofEpochSecond(500L, 10)); + } + + @Test + public void forInstant_minimum() { + testForInstant(Instant.MIN); + } + + @Test + public void forInstant_maximum() { + testForInstant(Instant.MAX); + } + + @Test + public void forInstant_null() { + testForInstant(null); + } + + private void testForInstant(Instant instant) { + Parcelling<Instant> parcelling = new ForInstant(); + parcelling.parcel(instant, mParcel, 0); + mParcel.setDataPosition(0); + + Instant created = parcelling.unparcel(mParcel); + + if (instant == null) { + assertNull(created); + } else { + assertEquals(instant, created); + } + } + +} diff --git a/tests/Internal/src/com/android/internal/util/UserIconsTest.java b/tests/Internal/src/com/android/internal/util/UserIconsTest.java new file mode 100644 index 000000000000..cc7b20b28a97 --- /dev/null +++ b/tests/Internal/src/com/android/internal/util/UserIconsTest.java @@ -0,0 +1,47 @@ +/* + * 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.internal.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UserIconsTest { + + @Test + public void convertToBitmapAtUserIconSize_sizeIsCorrect() { + Resources res = InstrumentationRegistry.getTargetContext().getResources(); + Drawable icon = UserIcons.getDefaultUserIcon(res, 0, true); + Bitmap bitmap = UserIcons.convertToBitmapAtUserIconSize(res, icon); + int expectedSize = res.getDimensionPixelSize(R.dimen.user_icon_size); + + assertThat(bitmap.getWidth()).isEqualTo(expectedSize); + assertThat(bitmap.getHeight()).isEqualTo(expectedSize); + } + +} diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java index e956be339bc4..dd9b294a9596 100644 --- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java +++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java @@ -82,11 +82,10 @@ public class JobStorePerfTests { long elapsedTimeNs = 0; while (benchmarkState.keepRunning(elapsedTimeNs)) { - sJobStore.clear(); + sJobStore.clearForTesting(); for (JobStatus job : jobList) { - sJobStore.add(job); + sJobStore.addForTesting(job); } - sJobStore.waitForWriteToCompleteForTesting(10_000); final long startTime = SystemClock.elapsedRealtimeNanos(); sJobStore.writeStatusToDiskForTesting(); @@ -110,11 +109,11 @@ public class JobStorePerfTests { long elapsedTimeNs = 0; while (benchmarkState.keepRunning(elapsedTimeNs)) { - sJobStore.clear(); + sJobStore.clearForTesting(); for (JobStatus job : jobList) { - sJobStore.add(job); + sJobStore.addForTesting(job); } - sJobStore.waitForWriteToCompleteForTesting(10_000); + sJobStore.writeStatusToDiskForTesting(); JobSet jobSet = new JobSet(); diff --git a/tests/LockTaskTests/Android.bp b/tests/LockTaskTests/Android.bp new file mode 100644 index 000000000000..dce681ead4b0 --- /dev/null +++ b/tests/LockTaskTests/Android.bp @@ -0,0 +1,32 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "LockTaskTests", + + privileged: true, + + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/I*.aidl", + "src/**/*.java", + ], + +} diff --git a/tests/LockTaskTests/Android.mk b/tests/LockTaskTests/Android.mk deleted file mode 100644 index 5406ee19041b..000000000000 --- a/tests/LockTaskTests/Android.mk +++ /dev/null @@ -1,19 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/priv-app - -LOCAL_PACKAGE_NAME := LockTaskTests -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_CERTIFICATE := platform - -LOCAL_SRC_FILES := $(call all-Iaidl-files-under, src) $(call all-java-files-under, src) - -include $(BUILD_PACKAGE) - -# Use the following include to make our test apk. -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/AppLaunchWear/Android.bp b/tests/MultiUser/Android.bp index e2fc4735a7c2..bde309fe3015 100644 --- a/tests/AppLaunchWear/Android.bp +++ b/tests/MultiUser/Android.bp @@ -8,15 +8,20 @@ package { } android_test { - name: "AppLaunchWear", - // Only compile source java files in this apk. - srcs: ["src/**/*.java"], + name: "MultiUserTests", platform_apis: true, - certificate: "platform", + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.test.rules", + "androidx.test.runner", + "services.core", + ], libs: [ - "android.test.base", "android.test.runner", + "android.test.base", + "android.test.mock", ], - static_libs: ["androidx.test.rules"], + certificate: "platform", test_suites: ["device-tests"], } diff --git a/tests/MultiUser/AndroidManifest.xml b/tests/MultiUser/AndroidManifest.xml new file mode 100644 index 000000000000..9a25c0990de4 --- /dev/null +++ b/tests/MultiUser/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.multiuser"> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test.multiuser"> + </instrumentation> +</manifest> diff --git a/tests/MultiUser/TEST_MAPPING b/tests/MultiUser/TEST_MAPPING new file mode 100644 index 000000000000..0dbef6cbc6ff --- /dev/null +++ b/tests/MultiUser/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "MultiUserTests" + } + ] +} diff --git a/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java b/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java new file mode 100644 index 000000000000..1521cc617873 --- /dev/null +++ b/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java @@ -0,0 +1,164 @@ +/* + * 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.test.multiuser; + +import static android.provider.Settings.Secure.FONT_WEIGHT_ADJUSTMENT; +import static android.provider.Settings.System.FONT_SCALE; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertEquals; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MultiUserSettingsTests { + private final Context mContext = getInstrumentation().getTargetContext(); + private final ContentResolver mContentResolver = mContext.getContentResolver(); + + private static void waitForBroadcastIdle() throws InterruptedException { + final int sleepDuration = 1000; + final String cmdAmWaitForBroadcastIdle = "am wait-for-broadcast-idle"; + + Thread.sleep(sleepDuration); + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand(cmdAmWaitForBroadcastIdle); + Thread.sleep(sleepDuration); + } + + private float getGlobalFontScale() { + return mContext.getResources().getConfiguration().fontScale; + } + + private int getGlobalFontWeight() { + return mContext.getResources().getConfiguration().fontWeightAdjustment; + } + + private float getFontScaleOfUser(int userId) { + return Settings.System.getFloatForUser(mContentResolver, FONT_SCALE, 1, userId); + } + + private int getFontWeightOfUser(int userId) { + return Settings.Secure.getIntForUser(mContentResolver, FONT_WEIGHT_ADJUSTMENT, 1, userId); + } + + private void setFontScaleOfUser(float fontScale, int userId) throws InterruptedException { + Settings.System.putFloatForUser(mContentResolver, FONT_SCALE, fontScale, userId); + waitForBroadcastIdle(); + } + + private void setFontWeightOfUser(int fontWeight, int userId) throws InterruptedException { + Settings.Secure.putIntForUser(mContentResolver, FONT_WEIGHT_ADJUSTMENT, fontWeight, userId); + waitForBroadcastIdle(); + } + + @Test + public void testChangingFontScaleOfABackgroundUser_shouldNotAffectUI() + throws InterruptedException { + + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + + UserManager userManager = UserManager.get(mContext); + + final int backgroundUserId = userManager.createUser("test_user", + UserManager.USER_TYPE_FULL_SECONDARY, 0).id; + final float oldFontScaleOfBgUser = getFontScaleOfUser(backgroundUserId); + final float oldGlobalFontScale = getGlobalFontScale(); + final float newFontScaleOfBgUser = 1 + Math.max(oldGlobalFontScale, oldFontScaleOfBgUser); + + try { + setFontScaleOfUser(newFontScaleOfBgUser, backgroundUserId); + final float newGlobalFontScale = getGlobalFontScale(); + assertEquals(oldGlobalFontScale, newGlobalFontScale, 0); + } finally { + setFontScaleOfUser(oldFontScaleOfBgUser, backgroundUserId); + userManager.removeUser(backgroundUserId); + } + } + + @Test + public void testChangingFontWeightOfABackgroundUser_shouldNotAffectUI() + throws InterruptedException { + + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + + UserManager userManager = UserManager.get(mContext); + + final int backgroundUserId = userManager.createUser("test_user", + UserManager.USER_TYPE_FULL_SECONDARY, 0).id; + final int oldFontWeightOfBgUser = getFontWeightOfUser(backgroundUserId); + final int oldGlobalFontWeight = getGlobalFontWeight(); + final int newFontWeightOfBgUser = 2 * Math.max(oldGlobalFontWeight, oldFontWeightOfBgUser); + + try { + setFontWeightOfUser(newFontWeightOfBgUser, backgroundUserId); + final int newGlobalFontWeight = getGlobalFontWeight(); + assertEquals(oldGlobalFontWeight, newGlobalFontWeight); + } finally { + setFontWeightOfUser(oldFontWeightOfBgUser, backgroundUserId); + userManager.removeUser(backgroundUserId); + } + } + + @Test + public void testChangingFontScaleOfTheForegroundUser_shouldAffectUI() + throws InterruptedException { + + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + + final int currentUserId = mContext.getUserId(); + final float oldFontScale = getFontScaleOfUser(currentUserId); + final float newFontScale = 1 + oldFontScale; + + try { + setFontScaleOfUser(newFontScale, currentUserId); + final float globalFontScale = getGlobalFontScale(); + assertEquals(newFontScale, globalFontScale, 0); + } finally { + setFontScaleOfUser(oldFontScale, currentUserId); + } + } + + @Test + public void testChangingFontWeightOfTheForegroundUser_shouldAffectUI() + throws InterruptedException { + + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + + final int currentUserId = mContext.getUserId(); + final int oldFontWeight = getFontWeightOfUser(currentUserId); + final int newFontWeight = 2 * oldFontWeight; + + try { + setFontWeightOfUser(newFontWeight, currentUserId); + final int globalFontWeight = getGlobalFontWeight(); + assertEquals(newFontWeight, globalFontWeight); + } finally { + setFontWeightOfUser(oldFontWeight, currentUserId); + } + } +} diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 6e1cef496f40..9f6ce4e8425b 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -31,7 +31,8 @@ android_test { test_config: "RollbackTest.xml", java_resources: [ ":com.android.apex.apkrollback.test_v2", - ":com.android.apex.apkrollback.test_v2Crashing" + ":com.android.apex.apkrollback.test_v2Crashing", + ":test.rebootless_apex_v2", ], } @@ -47,7 +48,10 @@ java_test_host { ], test_suites: ["general-tests"], test_config: "StagedRollbackTest.xml", - data: [":com.android.apex.apkrollback.test_v1"], + data: [ + ":com.android.apex.apkrollback.test_v1", + ":test.rebootless_apex_v1", + ], } java_test_host { diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 7b2a07fd80f8..cbdcb8869628 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -16,7 +16,6 @@ package com.android.tests.rollback; -import static com.android.cts.install.lib.InstallUtils.processUserData; import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; import static com.android.cts.rollback.lib.RollbackUtils.waitForAvailableRollback; @@ -31,11 +30,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.UserManager; -import android.provider.DeviceConfig; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -55,7 +52,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -71,12 +67,6 @@ public class RollbackTest { private static final String INSTRUMENTED_APP = "com.android.tests.rollback"; - // copied from PackageManagerService#PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS - // TODO: find a better place for the property so that it can be imported in tests - // maybe android.content.pm.PackageManager? - private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = - "enable_rollback_timeout"; - private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) { return rollbacks.stream().anyMatch( ri -> ri.getPackages().stream().anyMatch( @@ -203,740 +193,6 @@ public class RollbackTest { } } - /** - * Test that multiple available rollbacks are properly persisted. - */ - @Test - public void testAvailableRollbackPersistence() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - Uninstall.packages(TestApp.B); - Install.single(TestApp.B1).commit(); - Install.single(TestApp.B2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // Both test apps should now be available for rollback. - RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); - assertThat(rollbackA).isNotNull(); - assertThat(rollbackA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); - assertThat(rollbackB).isNotNull(); - assertThat(rollbackB).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Reload the persisted data. - rm.reloadPersistedData(); - - // The apps should still be available for rollback. - rollbackA = waitForAvailableRollback(TestApp.A); - assertThat(rollbackA).isNotNull(); - assertThat(rollbackA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - rollbackB = waitForAvailableRollback(TestApp.B); - assertThat(rollbackB).isNotNull(); - assertThat(rollbackB).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Rollback of B should not rollback A - RollbackUtils.rollback(rollbackB.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that available multi-package rollbacks are properly persisted. - */ - @Test - public void testAvailableMultiPackageRollbackPersistence() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A, TestApp.B); - Install.multi(TestApp.A1, TestApp.B1).commit(); - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // The app should now be available for rollback. - RollbackInfo availableA = waitForAvailableRollback(TestApp.A); - assertThat(availableA).isNotNull(); - assertThat(availableA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - RollbackInfo availableB = waitForAvailableRollback(TestApp.B); - assertThat(availableB).isNotNull(); - assertThat(availableB).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Assert they're both the same rollback - assertThat(availableA).hasRollbackId(availableB.getRollbackId()); - - // Reload the persisted data. - rm.reloadPersistedData(); - - // The apps should still be available for rollback. - availableA = waitForAvailableRollback(TestApp.A); - assertThat(availableA).isNotNull(); - assertThat(availableA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - availableB = waitForAvailableRollback(TestApp.B); - assertThat(availableB).isNotNull(); - assertThat(availableB).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Rollback of B should rollback A as well - RollbackUtils.rollback(availableB.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - - RollbackInfo committedA = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committedA).isNotNull(); - assertThat(committedA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - RollbackInfo committedB = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committedB).isNotNull(); - assertThat(committedB).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Assert they're both the same rollback - assertThat(committedA).hasRollbackId(committedB.getRollbackId()); - assertThat(committedA).hasRollbackId(availableA.getRollbackId()); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that recently committed rollback data is properly persisted. - */ - @Test - public void testRecentlyCommittedRollbackPersistence() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // The app should now be available for rollback. - RollbackInfo available = waitForAvailableRollback(TestApp.A); - assertThat(available).isNotNull(); - - // Roll back the app. - TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo", - /*versionCode*/ 42, /*isApex*/ false); - RollbackUtils.rollback(available.getRollbackId(), cause); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - // Verify the recent rollback has been recorded. - RollbackInfo committed = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committed).isNotNull(); - assertThat(committed).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(committed).causePackagesContainsExactly(cause); - - // Reload the persisted data. - rm.reloadPersistedData(); - - // Verify the recent rollback is still recorded. - committed = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committed).isNotNull(); - assertThat(committed).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(committed).causePackagesContainsExactly(cause); - assertThat(committed).hasRollbackId(available.getRollbackId()); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test the scheduling aspect of rollback expiration. - */ - @Test - public void testRollbackExpiresAfterLifetime() throws Exception { - long expirationTime = TimeUnit.SECONDS.toMillis(30); - long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(expirationTime), false /* makeDefault*/); - - // Uninstall TestApp.A - Uninstall.packages(TestApp.A); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - - // Install v1 of the app (without rollbacks enabled). - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - // Upgrade from v1 to v2, with rollbacks enabled. - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // Check that the rollback data has not expired - Thread.sleep(1000); - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Give it a little more time, but still not long enough to expire - Thread.sleep(expirationTime / 2); - rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Check that the data has expired after the expiration time (with a buffer of 1 second) - Thread.sleep(expirationTime / 2); - rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNull(); - - } finally { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(defaultExpirationTime), false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that available rollbacks should expire correctly when the property - * {@link RollbackManager#PROPERTY_ROLLBACK_LIFETIME_MILLIS} is changed - */ - @Test - public void testRollbackExpiresWhenLifetimeChanges() throws Exception { - long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - Uninstall.packages(TestApp.A); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Change the lifetime to 0 which should expire rollbacks immediately - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(0), false /* makeDefault*/); - - // Keep polling until device config changes has happened (which might take more than - // 5 sec depending how busy system_server is) and rollbacks have expired - for (int i = 0; i < 30; ++i) { - if (hasRollbackInclude(rm.getAvailableRollbacks(), TestApp.A)) { - Thread.sleep(1000); - } - } - rollback = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNull(); - } finally { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(defaultExpirationTime), false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that changing time on device does not affect the duration of time that we keep - * rollback available - */ - @Test - public void testTimeChangeDoesNotAffectLifetime() throws Exception { - long expirationTime = TimeUnit.SECONDS.toMillis(30); - long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG, - Manifest.permission.SET_TIME); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(expirationTime), false /* makeDefault*/); - - // Install app A with rollback enabled - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - Thread.sleep(expirationTime / 2); - - // Install app B with rollback enabled - Uninstall.packages(TestApp.B); - Install.single(TestApp.B1).commit(); - Install.single(TestApp.B2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // 1 second buffer - Thread.sleep(1000); - - try { - // Change the time - RollbackUtils.forwardTimeBy(expirationTime); - - // 1 second buffer to allow Rollback Manager to handle time change before loading - // persisted data - Thread.sleep(1000); - - // Load timestamps from storage - rm.reloadPersistedData(); - - // Wait until rollback for app A has expired - // This will trigger an expiration run that should expire app A but not B - Thread.sleep(expirationTime / 2); - RollbackInfo rollbackA = - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A); - Log.i(TAG, "Checking if the rollback for TestApp.A is null"); - - // Rollback for app B should not be expired - RollbackInfo rollbackB1 = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.B); - - // Wait until rollback for app B has expired - Thread.sleep(expirationTime / 2); - RollbackInfo rollbackB2 = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.B); - - assertThat(rollbackA).isNull(); - assertThat(rollbackB1).isNotNull(); - assertThat(rollbackB1).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - assertThat(rollbackB2).isNull(); - } finally { - RollbackUtils.forwardTimeBy(-expirationTime); - } - } finally { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(defaultExpirationTime), false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test explicit expiration of rollbacks. - * Does not test the scheduling aspects of rollback expiration. - */ - @Test - public void testRollbackExpiration() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // The app should now be available for rollback. - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Expire the rollback. - rm.expireRollbackForPackage(TestApp.A); - - // The rollback should no longer be available. - assertThat(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that app user data is rolled back. - */ - @Test - public void testUserDataRollback() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - processUserData(TestApp.A); - Install.single(TestApp.A2).setEnableRollback().commit(); - processUserData(TestApp.A); - - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - RollbackUtils.rollback(rollback.getRollbackId()); - processUserData(TestApp.A); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test rollback of apks involving splits. - */ - @Test - public void testRollbackWithSplits() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.ASplit1).commit(); - processUserData(TestApp.A); - - Install.single(TestApp.ASplit2).setEnableRollback().commit(); - processUserData(TestApp.A); - - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - RollbackUtils.rollback(rollback.getRollbackId()); - processUserData(TestApp.A); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test restrictions on rollback broadcast sender. - * A random app should not be able to send a ROLLBACK_COMMITTED broadcast. - */ - @Test - public void testRollbackBroadcastRestrictions() throws Exception { - RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); - Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED); - try { - InstrumentationRegistry.getContext().sendBroadcast(broadcast); - fail("Succeeded in sending restricted broadcast from app context."); - } catch (SecurityException se) { - // Expected behavior. - } - - // Confirm that we really haven't received the broadcast. - // TODO: How long to wait for the expected timeout? - assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull(); - - // TODO: Do we need to do this? Do we need to ensure this is always - // called, even when the test fails? - broadcastReceiver.unregister(); - } - - /** - * Regression test for rollback in the case when multiple apps are - * available for rollback at the same time. - */ - @Test - public void testMultipleRollbackAvailable() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - // Prep installation of the test apps. - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - Uninstall.packages(TestApp.B); - Install.single(TestApp.B1).commit(); - Install.single(TestApp.B2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // Both test apps should now be available for rollback, and the - // RollbackInfo returned for the rollbacks should be correct. - RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); - assertThat(rollbackA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); - assertThat(rollbackB).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Executing rollback should roll back the correct package. - RollbackUtils.rollback(rollbackA.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - RollbackUtils.rollback(rollbackB.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that the MANAGE_ROLLBACKS permission is required to call - * RollbackManager APIs. - */ - @Test - public void testManageRollbacksPermission() throws Exception { - // We shouldn't be allowed to call any of the RollbackManager APIs - // without the MANAGE_ROLLBACKS permission. - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - rm.getAvailableRollbacks(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.getRecentlyCommittedRollbacks(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - // TODO: What if the implementation checks arguments for non-null - // first? Then this test isn't valid. - rm.commitRollback(0, Collections.emptyList(), null); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.reloadPersistedData(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.expireRollbackForPackage(TestApp.A); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - } - - /** - * Test that you cannot enable rollback for a package without the - * MANAGE_ROLLBACKS permission. - */ - @Test - public void testEnableRollbackPermission() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().commit(); - - // We expect v2 of the app was installed, but rollback has not - // been enabled. - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat( - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that you cannot enable rollback for a non-module package when - * holding the MANAGE_ROLLBACKS permission. - */ - @Test - public void testNonModuleEnableRollback() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().commit(); - - // We expect v2 of the app was installed, but rollback has not - // been enabled because the test app is not a module. - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat( - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test rollback of multi-package installs is implemented. - */ - @Test - public void testMultiPackage() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - // Prep installation of the test apps. - Uninstall.packages(TestApp.A, TestApp.B); - Install.multi(TestApp.A1, TestApp.B1).commit(); - processUserData(TestApp.A); - processUserData(TestApp.B); - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - processUserData(TestApp.A); - processUserData(TestApp.B); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // TestApp.A should now be available for rollback. - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Rollback the app. It should cause both test apps to be rolled - // back. - RollbackUtils.rollback(rollback.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - - // We should see recent rollbacks listed for both A and B. - Thread.sleep(1000); - RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - - RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.B); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - assertThat(rollbackA).hasRollbackId(rollbackB.getRollbackId()); - - processUserData(TestApp.A); - processUserData(TestApp.B); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test failure to enable rollback for multi-package installs. - * If any one of the packages fail to enable rollback, we shouldn't enable - * rollback for any package. - */ - @Test - public void testMultiPackageEnableFail() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A, TestApp.B); - Install.single(TestApp.A1).commit(); - // We should fail to enable rollback here because TestApp B is not - // already installed. - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - assertThat(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A)).isNull(); - assertThat(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.B)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - @Test @Ignore("b/120200473") /** @@ -1074,195 +330,4 @@ public class RollbackTest { InstallUtils.dropShellPermissionIdentity(); } } - - @Test - public void testEnableRollbackTimeoutFailsRollback() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - //setting the timeout to a very short amount that will definitely be triggered - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - Long.toString(0), false /* makeDefault*/); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - waitForUnavailableRollback(TestApp.A); - - // Block the RollbackManager to make extra sure it will not be - // able to enable the rollback in time. - rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1)); - Install.single(TestApp.A2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // Give plenty of time for RollbackManager to unblock and attempt - // to make the rollback available before asserting that the - // rollback was not made available. - Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - assertThat( - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - //setting the timeout back to default - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - null, false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - @Test - public void testEnableRollbackTimeoutFailsRollback_MultiPackage() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - Long.toString(5000), false /* makeDefault*/); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A, TestApp.B); - Install.multi(TestApp.A1, TestApp.B1).commit(); - waitForUnavailableRollback(TestApp.A); - - // Block the 2nd session for 10s so it will not be able to enable the rollback in time. - rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(0)); - rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(10)); - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // Give plenty of time for RollbackManager to unblock and attempt - // to make the rollback available before asserting that the - // rollback was not made available. - Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - - List<RollbackInfo> available = rm.getAvailableRollbacks(); - assertThat(getUniqueRollbackInfoForPackage(available, TestApp.A)).isNull(); - assertThat(getUniqueRollbackInfoForPackage(available, TestApp.B)).isNull(); - } finally { - //setting the timeout back to default - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - null, false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test we can't enable rollback for non-whitelisted app without - * TEST_MANAGE_ROLLBACKS permission - */ - @Test - public void testNonRollbackWhitelistedApp() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull(); - - Install.single(TestApp.A2).setEnableRollback().commit(); - Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - @Test - public void testRollbackDataPolicy() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A, TestApp.B, TestApp.C); - Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit(); - // Write user data version = 1 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - Install a2 = Install.single(TestApp.A2) - .setEnableRollback(PackageManager.RollbackDataPolicy.WIPE); - Install b2 = Install.single(TestApp.B2) - .setEnableRollback(PackageManager.RollbackDataPolicy.RESTORE); - // The rollback data policy of C2 is specified in the manifest - Install c2 = Install.single(TestApp.C2).setEnableRollback(); - Install.multi(a2, b2, c2).setEnableRollback().commit(); - // Write user data version = 2 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A); - RollbackUtils.rollback(info.getRollbackId()); - // Read user data version from userdata.txt - // A's user data version is -1 for user data is wiped. - // B's user data version is 1 as rollback committed. - // C's user data version is -1 for user data is wiped. - assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1); - assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); - assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(-1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Tests an app can be rolled back to the previous signing key. - * - * <p>The rollback capability in the signing lineage allows an app to be updated to an APK - * signed with a previous signing key in the lineage; however this often defeats the purpose - * of key rotation as a compromised key could then be used to roll an app back to the previous - * key. To avoid requiring the rollback capability to support app rollbacks the PackageManager - * allows an app to be rolled back to the previous signing key if the rollback install reason - * is set. - */ - @Test - public void testRollbackAfterKeyRotation() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS); - - // Uninstall TestApp.A - Uninstall.packages(TestApp.A); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - - // Install v1 of the app with the original signing key (without rollbacks enabled). - Install.single(TestApp.AOriginal1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - // Upgrade from v1 to v2 with the rotated signing key, with rollbacks enabled. - Install.single(TestApp.ARotated2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // Roll back the app. - RollbackInfo available = waitForAvailableRollback(TestApp.A); - RollbackUtils.rollback(available.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 642b19e6d961..4cddcfeb91dc 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -22,12 +22,10 @@ import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoFo import static com.google.common.truth.Truth.assertThat; import android.Manifest; -import android.content.Context; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; -import android.os.storage.StorageManager; import android.provider.DeviceConfig; import androidx.test.platform.app.InstrumentationRegistry; @@ -60,6 +58,7 @@ import java.util.concurrent.TimeUnit; public class StagedRollbackTest { private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT = "watchdog_trigger_failure_count"; + private static final String REBOOTLESS_APEX_NAME = "test.apex.rebootless"; /** * Adopts common shell permissions needed for rollback tests. @@ -82,155 +81,6 @@ public class StagedRollbackTest { InstallUtils.dropShellPermissionIdentity(); } - /** - * Test rollbacks of staged installs involving only apks with bad update. - * Enable rollback phase. - */ - @Test - public void testBadApkOnly_Phase1_Install() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - - Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit(); - } - - /** - * Test rollbacks of staged installs involving only apks with bad update. - * Confirm that rollback was successfully enabled. - */ - @Test - public void testBadApkOnly_Phase2_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - InstallUtils.processUserData(TestApp.A); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(rollback.isStaged()).isTrue(); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, - Integer.toString(5), false); - RollbackUtils.sendCrashBroadcast(TestApp.A, 4); - // Sleep for a while to make sure we don't trigger rollback - Thread.sleep(TimeUnit.SECONDS.toMillis(30)); - } - - /** - * Test rollbacks of staged installs involving only apks. - * Confirm rollback phase. - */ - @Test - public void testBadApkOnly_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2); - assertThat(rollback).isStaged(); - assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1); - } - - /** - * Stage install an apk with rollback that will be later triggered by unattributable crash. - */ - @Test - public void testNativeWatchdogTriggersRollback_Phase1_Install() throws Exception { - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); - } - - /** - * Verify the rollback is available. - */ - @Test - public void testNativeWatchdogTriggersRollback_Phase2_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.A)).isNotNull(); - } - - /** - * Verify the rollback is committed after crashing. - */ - @Test - public void testNativeWatchdogTriggersRollback_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - TestApp.A)).isNotNull(); - } - - /** - * Stage install an apk with rollback that will be later triggered by unattributable crash. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase1_InstallA() throws Exception { - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); - } - - /** - * Verify the rollback is available and then install another package with rollback. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase2_InstallB() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.A)).isNotNull(); - - // Install another package with rollback - Install.single(TestApp.B1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - - Install.single(TestApp.B2).setEnableRollback().setStaged().commit(); - } - - /** - * Verify the rollbacks are available. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.A)).isNotNull(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.B)).isNotNull(); - } - - /** - * Verify the rollbacks are committed after crashing. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase4_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - TestApp.A)).isNotNull(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - TestApp.B)).isNotNull(); - } - @Test public void testPreviouslyAbandonedRollbacks_Phase1_InstallAndAbandon() throws Exception { Install.single(TestApp.A1).commit(); @@ -293,50 +143,6 @@ public class StagedRollbackTest { } @Test - public void testRollbackDataPolicy_Phase1_Install() throws Exception { - Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit(); - // Write user data version = 1 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - Install a2 = Install.single(TestApp.A2).setStaged() - .setEnableRollback(PackageManager.RollbackDataPolicy.WIPE); - Install b2 = Install.single(TestApp.B2).setStaged() - .setEnableRollback(PackageManager.RollbackDataPolicy.RESTORE); - // The rollback data policy of C2 is specified in the manifest - Install c2 = Install.single(TestApp.C2).setStaged().setEnableRollback(); - Install.multi(a2, b2, c2).setEnableRollback().setStaged().commit(); - } - - @Test - public void testRollbackDataPolicy_Phase2_Rollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - // Write user data version = 2 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A); - RollbackUtils.rollback(info.getRollbackId()); - } - - @Test - public void testRollbackDataPolicy_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.C)).isEqualTo(1); - // Read user data version from userdata.txt - // A's user data version is -1 for user data is wiped. - // B's user data version is 1 as rollback committed. - // C's user data version is -1 for user data is wiped. - assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1); - assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); - assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(-1); - } - - @Test public void expireRollbacks() throws Exception { // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks @@ -359,60 +165,14 @@ public class StagedRollbackTest { "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex"); - @Test - public void testRollbackApexWithApk_Phase1_Install() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - - int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() - .commit(); - InstallUtils.waitForSessionReady(sessionId); - } - - @Test - public void testRollbackApexWithApk_Phase2_Rollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - InstallUtils.processUserData(TestApp.A); - - RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); - assertThat(available).isStaged(); - assertThat(available).packagesContainsExactly( - Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), - Rollback.from(TestApp.A, 0).to(TestApp.A1)); - - RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); - RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); - assertThat(committed).isNotNull(); - assertThat(committed).isStaged(); - assertThat(committed).packagesContainsExactly( - Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), - Rollback.from(TestApp.A, 0).to(TestApp.A1)); - assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); - assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); - - // Note: The app is not rolled back until after the rollback is staged - // and the device has been rebooted. - InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); - assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); - } - - @Test - public void testRollbackApexWithApk_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - } - /** * Installs an apex with an apk that can crash. */ @Test public void testRollbackApexWithApkCrashing_Phase1_Install() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged() + Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged() .setEnableRollback().commit(); - InstallUtils.waitForSessionReady(sessionId); } /** @@ -442,52 +202,6 @@ public class StagedRollbackTest { } @Test - public void testRollbackApexDataDirectories_Phase1_Install() throws Exception { - int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() - .commit(); - InstallUtils.waitForSessionReady(sessionId); - } - - @Test - public void testRollbackApexDataDirectories_Phase2_Rollback() throws Exception { - RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); - - RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); - RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); - - // Note: The app is not rolled back until after the rollback is staged - // and the device has been rebooted. - InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); - } - - @Test - public void testRollbackApkDataDirectories_Phase1_InstallV1() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - } - - @Test - public void testRollbackApkDataDirectories_Phase2_InstallV2() throws Exception { - Install.single(TestApp.A2).setStaged().setEnableRollback().commit(); - } - - @Test - public void testRollbackApkDataDirectories_Phase3_Rollback() throws Exception { - RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A); - RollbackUtils.rollback(available.getRollbackId(), TestApp.A2); - RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); - InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); - } - - @Test - public void isCheckpointSupported() { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); - StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - assertThat(sm.isCheckpointSupported()).isTrue(); - } - - @Test public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); Install.single(TestApp.A1).commit(); @@ -527,30 +241,49 @@ public class StagedRollbackTest { } @Test - public void testExpireSession_Phase1_Install() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); + public void testRollbackRebootlessApex() throws Exception { + final String packageName = REBOOTLESS_APEX_NAME; + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); + + // install + TestApp apex1 = new TestApp("TestRebootlessApexV1", packageName, 1, + /* isApex= */ true, "test.rebootless_apex_v1.apex"); + TestApp apex2 = new TestApp("TestRebootlessApexV2", packageName, 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN) + .commit(); + + // verify rollback + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(2); + RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(packageName); + assertThat(rollback).packagesContainsExactly(Rollback.from(apex2).to(apex1)); + assertThat(rollback).isNotStaged(); + + // rollback + RollbackUtils.rollback(rollback.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); } @Test - public void testExpireSession_Phase2_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(rollback.isStaged()).isTrue(); + public void testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install() throws Exception { + assertThat(InstallUtils.getInstalledVersion(REBOOTLESS_APEX_NAME)).isEqualTo(1); + + TestApp apex2 = new TestApp("TestRebootlessApexV2", REBOOTLESS_APEX_NAME, 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN) + .commit(); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + + RollbackUtils.waitForAvailableRollback(TestApp.A); + RollbackUtils.waitForAvailableRollback(REBOOTLESS_APEX_NAME); } @Test - public void testExpireSession_Phase3_VerifyRollback() throws Exception { - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); + public void testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify() throws Exception { + // Check only rebootless apex is rolled back. Other rollbacks should remain unchanged. + assertThat(RollbackUtils.getCommittedRollback(REBOOTLESS_APEX_NAME)).isNotNull(); + assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull(); } @Test diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 1aa5c249ff18..05e8bde23008 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -21,13 +21,9 @@ import static com.android.tests.rollback.host.WatchdogEventLogger.Subject.assert import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; -import com.android.ddmlib.Log; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.IFileEntry; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; @@ -40,12 +36,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; -import java.time.Instant; -import java.util.Collections; -import java.util.Date; -import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; /** * Runs the staged rollback tests. @@ -72,17 +63,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A"; - private static final String TEST_SUBDIR = "/subdir/"; - - private static final String TEST_FILENAME_1 = "test_file.txt"; - private static final String TEST_STRING_1 = "hello this is a test"; - private static final String TEST_FILENAME_2 = "another_file.txt"; - private static final String TEST_STRING_2 = "this is a different file"; - private static final String TEST_FILENAME_3 = "also.xyz"; - private static final String TEST_STRING_3 = "also\n a\n test\n string"; - private static final String TEST_FILENAME_4 = "one_more.test"; - private static final String TEST_STRING_4 = "once more unto the test"; - private static final String REASON_APP_CRASH = "REASON_APP_CRASH"; private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH"; @@ -116,7 +96,9 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "*", - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*"); + apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*", + "/system/apex/test.rebootless_apex_v*.apex", + "/data/apex/active/test.apex.rebootless*.apex"); } /** @@ -124,26 +106,26 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { * @param files the paths of files which might contain wildcards */ private void deleteFiles(String... files) throws Exception { - boolean found = false; - for (String file : files) { - CommandResult result = getDevice().executeShellV2Command("ls " + file); - if (result.getStatus() == CommandStatus.SUCCESS) { - found = true; - break; + try { + getDevice().enableAdbRoot(); + boolean found = false; + for (String file : files) { + CommandResult result = getDevice().executeShellV2Command("ls " + file); + if (result.getStatus() == CommandStatus.SUCCESS) { + found = true; + break; + } } - } - if (found) { - try { - getDevice().enableAdbRoot(); + if (found) { getDevice().remountSystemWritable(); for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); } - } finally { - getDevice().disableAdbRoot(); + getDevice().reboot(); } - getDevice().reboot(); + } finally { + getDevice().disableAdbRoot(); } } @@ -153,98 +135,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } /** - * Tests watchdog triggered staged rollbacks involving only apks. - */ - @Test - public void testBadApkOnly() throws Exception { - runPhase("testBadApkOnly_Phase1_Install"); - getDevice().reboot(); - runPhase("testBadApkOnly_Phase2_VerifyInstall"); - - // Launch the app to crash to trigger rollback - startActivity(TESTAPP_A); - // Wait for reboot to happen - waitForDeviceNotAvailable(2, TimeUnit.MINUTES); - - getDevice().waitForDeviceAvailable(); - - runPhase("testBadApkOnly_Phase3_VerifyRollback"); - - assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A); - assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); - assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); - } - - @Test - public void testNativeWatchdogTriggersRollback() throws Exception { - runPhase("testNativeWatchdogTriggersRollback_Phase1_Install"); - - // Reboot device to activate staged package - getDevice().reboot(); - - runPhase("testNativeWatchdogTriggersRollback_Phase2_VerifyInstall"); - - // crash system_server enough times to trigger a rollback - crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); - - // Rollback should be committed automatically now. - // Give time for rollback to be committed. This could take a while, - // because we need all of the following to happen: - // 1. system_server comes back up and boot completes. - // 2. Rollback health observer detects updatable crashing signal. - // 3. Staged rollback session becomes ready. - // 4. Device actually reboots. - // So we give a generous timeout here. - waitForDeviceNotAvailable(5, TimeUnit.MINUTES); - getDevice().waitForDeviceAvailable(); - - // verify rollback committed - runPhase("testNativeWatchdogTriggersRollback_Phase3_VerifyRollback"); - - assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null); - assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); - assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); - } - - @Test - public void testNativeWatchdogTriggersRollbackForAll() throws Exception { - // This test requires committing multiple staged rollbacks - assumeTrue(isCheckpointSupported()); - - // Install a package with rollback enabled. - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1_InstallA"); - getDevice().reboot(); - - // Once previous staged install is applied, install another package - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2_InstallB"); - getDevice().reboot(); - - // Verify the new staged install has also been applied successfully. - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyInstall"); - - // crash system_server enough times to trigger a rollback - crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); - - // Rollback should be committed automatically now. - // Give time for rollback to be committed. This could take a while, - // because we need all of the following to happen: - // 1. system_server comes back up and boot completes. - // 2. Rollback health observer detects updatable crashing signal. - // 3. Staged rollback session becomes ready. - // 4. Device actually reboots. - // So we give a generous timeout here. - waitForDeviceNotAvailable(5, TimeUnit.MINUTES); - getDevice().waitForDeviceAvailable(); - - // verify all available rollbacks have been committed - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4_VerifyRollback"); - - assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null); - assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); - assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); - } - - /** * Tests rolling back user data where there are multiple rollbacks for that package. */ @Test @@ -267,44 +157,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testRollbackAllowlistedApp_Phase2_VerifyInstall"); } - @Test - public void testRollbackDataPolicy() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_ce/0/rollback"); - - runPhase("testRollbackDataPolicy_Phase1_Install"); - getDevice().reboot(); - runPhase("testRollbackDataPolicy_Phase2_Rollback"); - getDevice().reboot(); - runPhase("testRollbackDataPolicy_Phase3_VerifyRollback"); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc_ce/0/rollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_ce/0/rollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); - } - - /** - * Tests that userdata of apk-in-apex is restored when apex is rolled back. - */ - @Test - public void testRollbackApexWithApk() throws Exception { - pushTestApex(); - runPhase("testRollbackApexWithApk_Phase1_Install"); - getDevice().reboot(); - runPhase("testRollbackApexWithApk_Phase2_Rollback"); - getDevice().reboot(); - runPhase("testRollbackApexWithApk_Phase3_VerifyRollback"); - } - /** * Tests that RollbackPackageHealthObserver is observing apk-in-apex. */ @Test public void testRollbackApexWithApkCrashing() throws Exception { - pushTestApex(); + pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); // Install an apex with apk that crashes runPhase("testRollbackApexWithApkCrashing_Phase1_Install"); @@ -325,239 +183,24 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } /** - * Tests that data in DE_sys apex data directory is restored when apex is rolled back. - */ - @Test - public void testRollbackApexDataDirectories_DeSys() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "/" + TEST_FILENAME_3; - String newFilePath4 = - apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); - } - - /** - * Tests that data in DE (user) apex data directory is restored when apex is rolled back. + * Tests rollback is supported correctly for rebootless apex */ @Test - public void testRollbackApexDataDirectories_DeUser() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_de/0/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirDeUser( - APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = - apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_3; - String newFilePath4 = - apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc_de/0/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_de/0/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); - } - - /** - * Tests that data in CE apex data directory is restored when apex is rolled back. - */ - @Test - public void testRollbackApexDataDirectories_Ce() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_3; - String newFilePath4 = - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); + public void testRollbackRebootlessApex() throws Exception { + pushTestApex("test.rebootless_apex_v1.apex"); + runPhase("testRollbackRebootlessApex"); } /** - * Tests that data in DE apk data directory is restored when apk is rolled back. + * Tests only rebootless apex (if any) is rolled back when native crash happens */ @Test - public void testRollbackApkDataDirectories_De() throws Exception { - // Install version 1 of TESTAPP_A - runPhase("testRollbackApkDataDirectories_Phase1_InstallV1"); - - // Push files to apk data directory - String oldFilePath1 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install version 2 of TESTAPP_A with rollback enabled - runPhase("testRollbackApkDataDirectories_Phase2_InstallV2"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_3; - String newFilePath4 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APK - runPhase("testRollbackApkDataDirectories_Phase3_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - } - - @Test - public void testExpireApexRollback() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - // Expire all rollbacks and check CE snapshot directories are deleted - runPhase("expireRollbacks"); - runAsRoot(() -> { - for (String dir : after) { - assertFileNotExists(dir); - } - }); + public void testNativeWatchdogTriggersRebootlessApexRollback() throws Exception { + pushTestApex("test.rebootless_apex_v1.apex"); + runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install"); + crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); + getDevice().waitForDeviceAvailable(); + runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify"); } /** @@ -584,30 +227,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback"); } - /** - * Tests an available rollback shouldn't be deleted when its session expires. - */ - @Test - public void testExpireSession() throws Exception { - runPhase("testExpireSession_Phase1_Install"); - getDevice().reboot(); - runPhase("testExpireSession_Phase2_VerifyInstall"); - - // Advance system clock by 7 days to expire the staged session - Instant t1 = Instant.ofEpochMilli(getDevice().getDeviceDate()); - Instant t2 = t1.plusMillis(TimeUnit.DAYS.toMillis(7)); - runAsRoot(() -> getDevice().setDate(Date.from(t2))); - - // Somehow we need to wait for a while before reboot. Otherwise the change to the - // system clock will be reset after reboot. - Thread.sleep(3000); - getDevice().reboot(); - runPhase("testExpireSession_Phase3_VerifyRollback"); - } - - private void pushTestApex() throws Exception { + private void pushTestApex(String fileName) throws Exception { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); - final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; final File apex = buildHelper.getTestFile(fileName); try { getDevice().enableAdbRoot(); @@ -619,101 +240,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().reboot(); } - private void pushString(String contents, String path) throws Exception { - assertWithMessage("Failed to push file to device, content=%s path=%s", contents, path) - .that(getDevice().pushString(contents, path)).isTrue(); - } - - private void assertFileContents(String expectedContents, String path) throws Exception { - String actualContents = getDevice().pullFileContents(path); - assertWithMessage("Failed to retrieve file=%s", path).that(actualContents).isNotNull(); - assertWithMessage("Mismatched file contents, path=%s", path) - .that(actualContents).isEqualTo(expectedContents); - } - - private void assertFileNotExists(String path) throws Exception { - assertWithMessage("File shouldn't exist, path=%s", path) - .that(getDevice().getFileEntry(path)).isNull(); - } - private static String apexDataDirDeSys(String apexName) { return String.format("/data/misc/apexdata/%s", apexName); } - private static String apexDataDirDeUser(String apexName, int userId) { - return String.format("/data/misc_de/%d/apexdata/%s", userId, apexName); - } - private static String apexDataDirCe(String apexName, int userId) { return String.format("/data/misc_ce/%d/apexdata/%s", userId, apexName); } - private static String apkDataDirDe(String apexName, int userId) { - return String.format("/data/user_de/%d/%s", userId, apexName); - } - - private List<String> getSnapshotDirectories(String baseDir) throws Exception { - try { - getDevice().enableAdbRoot(); - IFileEntry f = getDevice().getFileEntry(baseDir); - if (f == null) { - Log.d(TAG, "baseDir doesn't exist: " + baseDir); - return Collections.EMPTY_LIST; - } - List<String> list = f.getChildren(false) - .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?")) - .map(entry -> entry.getFullPath()) - .collect(Collectors.toList()); - Log.d(TAG, "getSnapshotDirectories=" + list); - return list; - } finally { - getDevice().disableAdbRoot(); - } - } - - private void assertDirectoryIsEmpty(String path) throws Exception { - try { - getDevice().enableAdbRoot(); - IFileEntry file = getDevice().getFileEntry(path); - assertWithMessage("Not a directory: " + path).that(file.isDirectory()).isTrue(); - assertWithMessage("Directory not empty: " + path) - .that(file.getChildren(false)).isEmpty(); - } catch (DeviceNotAvailableException e) { - fail("Can't access directory: " + path); - } finally { - getDevice().disableAdbRoot(); - } - } - private void startActivity(String packageName) throws Exception { String cmd = "am start -S -a android.intent.action.MAIN " + "-c android.intent.category.LAUNCHER " + packageName; getDevice().executeShellCommand(cmd); } - private void crashProcess(String processName, int numberOfCrashes) throws Exception { - String pid = ""; - String lastPid = "invalid"; - for (int i = 0; i < numberOfCrashes; ++i) { - // This condition makes sure before we kill the process, the process is running AND - // the last crash was finished. - while ("".equals(pid) || lastPid.equals(pid)) { - pid = getDevice().executeShellCommand("pidof " + processName); - } - getDevice().executeShellCommand("kill " + pid); - lastPid = pid; - } - } - - private boolean isCheckpointSupported() throws Exception { - try { - runPhase("isCheckpointSupported"); - return true; - } catch (AssertionError ignore) { - return false; - } - } - /** * True if this build has mainline modules installed. */ @@ -726,17 +266,17 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } } - @FunctionalInterface - private interface ExceptionalRunnable { - void run() throws Exception; - } - - private void runAsRoot(ExceptionalRunnable runnable) throws Exception { - try { - getDevice().enableAdbRoot(); - runnable.run(); - } finally { - getDevice().disableAdbRoot(); + private void crashProcess(String processName, int numberOfCrashes) throws Exception { + String pid = ""; + String lastPid = "invalid"; + for (int i = 0; i < numberOfCrashes; ++i) { + // This condition makes sure before we kill the process, the process is running AND + // the last crash was finished. + while ("".equals(pid) || lastPid.equals(pid)) { + pid = getDevice().executeShellCommand("pidof " + processName); + } + getDevice().executeShellCommand("kill " + pid); + lastPid = pid; } } } diff --git a/tests/SharedLibraryLoadingTest/Android.bp b/tests/SharedLibraryLoadingTest/Android.bp new file mode 100644 index 000000000000..088278d6ee89 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/Android.bp @@ -0,0 +1,37 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_test_host { + name: "SharedLibraryLoadingTests", + libs: [ + "tradefed", + "junit", + ], + test_suites: ["general-tests"], + data: [ + ":SharedLibraryLoadingTests_StandardSharedLibrary", + ":SharedLibraryLoadingTests_SharedLibraryLoadedAfter", + ":SharedLibraryLoadingTests_SharedLibraryClientTests", + ":SharedLibraryLoadingTests_Overlay", + ], +} diff --git a/tests/SharedLibraryLoadingTest/AndroidTest.xml b/tests/SharedLibraryLoadingTest/AndroidTest.xml new file mode 100644 index 000000000000..947453d07bd9 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/AndroidTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<configuration description="Host-driven test module config for SharedLibraryHostTests"> + <option name="test-tag" value="SharedLibraryLoadingTests" /> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="false" /> + <option name="remount-system" value="true" /> + <option name="push" + value="SharedLibraryLoadingTests_StandardSharedLibrary.apk->/product/app/SharedLibraryLoadingTests_StandardSharedLibrary.apk" /> + <option name="push" + value="SharedLibraryLoadingTests_SharedLibraryLoadedAfter.apk->/product/app/SharedLibraryLoadingTests_SharedLibraryLoadedAfter.apk" /> + <option name="push" + value="SharedLibraryLoadingTests_Overlay.apk->/product/overlay/SharedLibraryLoadingTests_Overlay.apk" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" /> + + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="SharedLibraryLoadingTests_SharedLibraryClientTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.sharedlibloadingtest.client" /> + </test> +</configuration>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/OWNERS b/tests/SharedLibraryLoadingTest/OWNERS new file mode 100644 index 000000000000..d7b4569b6bc0 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/OWNERS @@ -0,0 +1,2 @@ +stenning@google.com + diff --git a/tests/SharedLibraryLoadingTest/test-apps/Overlay/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/Overlay/Android.bp new file mode 100644 index 000000000000..b2f4e8925b58 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/Overlay/Android.bp @@ -0,0 +1,29 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "SharedLibraryLoadingTests_Overlay", + platform_apis: true, + certificate: "platform", + aaptflags: ["--no-resource-removal"], +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/Overlay/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/Overlay/AndroidManifest.xml new file mode 100644 index 000000000000..ae2784ca0904 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/Overlay/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.sharedlibloadingtest.overlay"> + <application android:hasCode="false" /> + <overlay android:targetPackage="android" + android:isStatic="true" + android:priority="1"/> +</manifest>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/Overlay/res/values/config.xml b/tests/SharedLibraryLoadingTest/test-apps/Overlay/res/values/config.xml new file mode 100644 index 000000000000..15da3dbafd84 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/Overlay/res/values/config.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <string-array name="config_sharedLibrariesLoadedAfterApp" translatable="false"> + <item>com.android.sharedlibloadingtest.shared_library_after</item> + </string-array> +</resources>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/Android.bp new file mode 100644 index 000000000000..0d204979cb92 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/Android.bp @@ -0,0 +1,35 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "SharedLibraryLoadingTests_SharedLibraryClientTests", + srcs: ["**/*.java"], + resource_dirs: ["res"], + libs: [ + "SharedLibraryLoadingTests_StandardSharedLibrary", + "SharedLibraryLoadingTests_SharedLibraryLoadedAfter", + "android.test.base", + ], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "androidx.test.core", + "testng", + ], + platform_apis: true, +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/AndroidManifest.xml new file mode 100644 index 000000000000..e3a9b9bca78a --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.sharedlibloadingtest.client"> + <application> + <uses-library android:name="android.test.runner" /> + <uses-library android:name="com.android.sharedlibloadingtest.shared_library"/> + <uses-library android:name="com.android.sharedlibloadingtest.shared_library_after"/> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.sharedlibloadingtest.client" /> +</manifest>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/res/values/values.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/res/values/values.xml new file mode 100644 index 000000000000..5e0544eb8696 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <string name="identical_resource_key">client value</string> +</resources>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/ClientClass.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/ClientClass.java new file mode 100644 index 000000000000..e48fb833bd76 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/ClientClass.java @@ -0,0 +1,24 @@ +/* + * 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.sharedlibloadingtest; + +public class ClientClass { + @Override + public String toString() { + return "Client Code"; + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassA.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassA.java new file mode 100644 index 000000000000..4c771557e119 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassA.java @@ -0,0 +1,24 @@ +/* + * 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.sharedlibloadingtest; + +public class DuplicateClassA { + @Override + public String toString() { + return "Client's Version"; + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassB.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassB.java new file mode 100644 index 000000000000..86aa6a1a0901 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassB.java @@ -0,0 +1,24 @@ +/* + * 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.sharedlibloadingtest; + +public class DuplicateClassB { + @Override + public String toString() { + return "Client's Version B"; + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/client/SharedLibraryLoadingOrderTest.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/client/SharedLibraryLoadingOrderTest.java new file mode 100644 index 000000000000..43bcb1ad7d27 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/client/SharedLibraryLoadingOrderTest.java @@ -0,0 +1,90 @@ +/* + * 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.sharedlibloadingtest.client; + +import static org.testng.Assert.assertEquals; + +import android.content.Context; +import android.content.res.Resources; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.util.Preconditions; +import com.android.sharedlibloadingtest.ClientClass; +import com.android.sharedlibloadingtest.DuplicateClassA; +import com.android.sharedlibloadingtest.DuplicateClassB; +import com.android.sharedlibloadingtest.SharedClassAfter; +import com.android.sharedlibloadingtest.StdSharedClass; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.HashSet; + +@RunWith(AndroidJUnit4.class) +public class SharedLibraryLoadingOrderTest { + + @Test + public void testLoadingOfStdShareLibsShouldBeFirst() { + Preconditions.checkArgument(!getLibsLoadedAfter() + .contains("com.android.sharedlibloadingtest.shared_library")); + DuplicateClassA clazz = new DuplicateClassA(); + assertEquals(clazz.toString(), "Standard Shared Lib's Version"); + + StdSharedClass stdSharedClass = new StdSharedClass(); + assertEquals(stdSharedClass.toString(), "Nothing Special Lib"); + + ClientClass clientCode = new ClientClass(); + assertEquals(clientCode.toString(), "Client Code"); + } + + @Test + public void testLoadingOfShareLibsIsAfter() { + Preconditions.checkArgument(getLibsLoadedAfter() + .contains("com.android.sharedlibloadingtest.shared_library_after")); + DuplicateClassB clazz = new DuplicateClassB(); + assertEquals(clazz.toString(), "Client's Version B"); + + SharedClassAfter stdSharedClass = new SharedClassAfter(); + assertEquals(stdSharedClass.toString(), "Also Nothing Special"); + + ClientClass clientCode = new ClientClass(); + assertEquals(clientCode.toString(), "Client Code"); + } + + @Test + public void testLoadingOfResource() { + // aapt compiler gives each lib their own namespace so this test just confirming + // the resources can be loaded from the same context object + Context context = ApplicationProvider.getApplicationContext(); + String clientString = context.getResources().getString(R.string.identical_resource_key); + assertEquals(clientString, "client value"); + assertEquals(StdSharedClass.getResString(context), "std lib value"); + assertEquals(SharedClassAfter.getResString(context), "loaded after value"); + + } + + private HashSet<String> getLibsLoadedAfter() { + Resources systemR = Resources.getSystem(); + HashSet<String> libsToLoadAfter = new HashSet<>(); + Collections.addAll(libsToLoadAfter, systemR.getStringArray( + com.android.internal.R.array.config_sharedLibrariesLoadedAfterApp)); + return libsToLoadAfter; + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/Android.bp new file mode 100644 index 000000000000..db9b3edfe6a2 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/Android.bp @@ -0,0 +1,30 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "SharedLibraryLoadingTests_SharedLibraryLoadedAfter", + srcs: ["**/*.java"], + resource_dirs: ["res"], + sdk_version: "current", + aaptflags: ["--shared-lib"], +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/AndroidManifest.xml new file mode 100644 index 000000000000..efedfcfeb515 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.sharedlibloadingtest.shared_library_after"> + <application> + <library android:name="com.android.sharedlibloadingtest.shared_library_after" /> + </application> +</manifest>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/res/values/values.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/res/values/values.xml new file mode 100644 index 000000000000..4525944b060c --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <string name="identical_resource_key">loaded after value</string> +</resources>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/DuplicateClassB.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/DuplicateClassB.java new file mode 100644 index 000000000000..1e1f5aab5993 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/DuplicateClassB.java @@ -0,0 +1,24 @@ +/* + * 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.sharedlibloadingtest; + +public class DuplicateClassB { + @Override + public String toString() { + return "Loaded After Shared Lib's Version"; + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/SharedClassAfter.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/SharedClassAfter.java new file mode 100644 index 000000000000..9e5b40fc38d8 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/SharedClassAfter.java @@ -0,0 +1,32 @@ +/* + * 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.sharedlibloadingtest; + +import android.content.Context; + +import com.android.sharedlibloadingtest.shared_library_after.R; + +public class SharedClassAfter { + @Override + public String toString() { + return "Also Nothing Special"; + } + + public static String getResString(Context context) { + return context.getResources().getString(R.string.identical_resource_key); + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/Android.bp new file mode 100644 index 000000000000..50456b0439c2 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/Android.bp @@ -0,0 +1,30 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "SharedLibraryLoadingTests_StandardSharedLibrary", + srcs: ["**/*.java"], + resource_dirs: ["res"], + sdk_version: "current", + aaptflags: ["--shared-lib"], +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/AndroidManifest.xml new file mode 100644 index 000000000000..f1a079feb316 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.sharedlibloadingtest.shared_library"> + <application> + <library android:name="com.android.sharedlibloadingtest.shared_library" /> + </application> +</manifest>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/res/values/values.xml b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/res/values/values.xml new file mode 100644 index 000000000000..941351aaea62 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <string name="identical_resource_key">std lib value</string> +</resources>
\ No newline at end of file diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/DuplicateClassA.java b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/DuplicateClassA.java new file mode 100644 index 000000000000..a3874aa3ad96 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/DuplicateClassA.java @@ -0,0 +1,24 @@ +/* + * 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.sharedlibloadingtest; + +public class DuplicateClassA { + @Override + public String toString() { + return "Standard Shared Lib's Version"; + } +} diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/StdSharedClass.java b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/StdSharedClass.java new file mode 100644 index 000000000000..429d65ca2439 --- /dev/null +++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/StdSharedClass.java @@ -0,0 +1,32 @@ +/* + * 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.sharedlibloadingtest; + +import android.content.Context; + +import com.android.sharedlibloadingtest.shared_library.R; + +public class StdSharedClass { + @Override + public String toString() { + return "Nothing Special Lib"; + } + + public static String getResString(Context context) { + return context.getResources().getString(R.string.identical_resource_key); + } +} diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml index c30d76137f76..21256d8c9d0b 100644 --- a/tests/SilkFX/AndroidManifest.xml +++ b/tests/SilkFX/AndroidManifest.xml @@ -20,17 +20,20 @@ <uses-sdk android:minSdkVersion="30"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:label="SilkFX" android:theme="@android:style/Theme.Material"> <activity android:name=".Main" android:label="SilkFX Demos" + android:banner="@drawable/background1" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> </intent-filter> </activity> @@ -41,13 +44,16 @@ <activity android:name=".materials.GlassActivity" android:label="Glass Examples" - android:banner="@drawable/background1" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> </activity> + <activity android:name=".materials.BackgroundBlurActivity" + android:theme="@style/Theme.BackgroundBlurTheme" + android:exported="true"> + </activity> + </application> </manifest> diff --git a/tests/SilkFX/res/drawable/background_blur_drawable.xml b/tests/SilkFX/res/drawable/background_blur_drawable.xml new file mode 100644 index 000000000000..173ca99bdfdf --- /dev/null +++ b/tests/SilkFX/res/drawable/background_blur_drawable.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#20FFFFFF"/> + <corners android:radius="10dp"/> +</shape> diff --git a/tests/SilkFX/res/drawable/blur_activity_background_drawable_white.xml b/tests/SilkFX/res/drawable/blur_activity_background_drawable_white.xml new file mode 100644 index 000000000000..bd8942d46383 --- /dev/null +++ b/tests/SilkFX/res/drawable/blur_activity_background_drawable_white.xml @@ -0,0 +1,20 @@ +<?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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="10dp"/> +</shape> diff --git a/tests/SilkFX/res/layout/activity_background_blur.xml b/tests/SilkFX/res/layout/activity_background_blur.xml new file mode 100644 index 000000000000..f13c0883cb01 --- /dev/null +++ b/tests/SilkFX/res/layout/activity_background_blur.xml @@ -0,0 +1,173 @@ +<?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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/background" + android:layout_width="390dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:padding="15dp" + android:orientation="vertical" + tools:context=".materials.BackgroundBlurActivity"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:padding="10dp" + android:textColor="#ffffffff" + android:text="Hello blurry world!"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Background blur"/> + + <SeekBar + android:id="@+id/set_background_blur" + android:min="0" + android:max="300" + android:layout_width="160dp" + android:layout_height="wrap_content"/> + <TextView + android:id="@+id/background_blur_radius" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:ems="3" + android:gravity="center" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Background alpha"/> + + <SeekBar + android:id="@+id/set_background_alpha" + android:min="0" + android:max="100" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/background_alpha" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:ems="3" + android:gravity="center" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Blur behind"/> + + <SeekBar + android:id="@+id/set_blur_behind" + android:min="0" + android:max="300" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/blur_behind_radius" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="#ffffffff" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:ems="3" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Dim amount"/> + + <SeekBar + android:id="@+id/set_dim_amount" + android:min="0" + android:max="100" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/dim_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="#ffffffff" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:ems="3" + android:text="TODO"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="5dp" + android:orientation="vertical" + android:gravity="center"> + + <Button + android:id="@+id/toggle_blur_enabled" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Disable blur" + android:onClick="toggleForceBlurDisabled"/> + + <Button + android:id="@+id/toggle_battery_saving_mode" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="TODO" + android:onClick="toggleBatterySavingMode"/> + </LinearLayout> + <requestFocus/> + +</LinearLayout> diff --git a/tests/SilkFX/res/values/style.xml b/tests/SilkFX/res/values/style.xml new file mode 100644 index 000000000000..66edbb5c9382 --- /dev/null +++ b/tests/SilkFX/res/values/style.xml @@ -0,0 +1,31 @@ +<?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. + --> +<!-- Styles for immersive actions UI. --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <style name="Theme.BackgroundBlurTheme" parent= "Theme.AppCompat.Dialog"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBlurBehindEnabled">true</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowElevation">0dp</item> + <item name="buttonStyle">@style/AppTheme.Button</item> + <item name="colorAccent">#bbffffff</item> + </style> + <style name="AppTheme.Button" parent="Widget.AppCompat.Button"> + <item name="android:textColor">#ffffffff</item> + </style> + +</resources> diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt index 9ed8d2f5edf7..7132ae8772ea 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/Main.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -30,6 +30,7 @@ import com.android.test.silkfx.app.EXTRA_LAYOUT import com.android.test.silkfx.app.EXTRA_TITLE import com.android.test.silkfx.hdr.GlowActivity import com.android.test.silkfx.materials.GlassActivity +import com.android.test.silkfx.materials.BackgroundBlurActivity import kotlin.reflect.KClass class Demo(val name: String, val makeIntent: (Context) -> Intent) { @@ -51,7 +52,8 @@ private val AllDemos = listOf( Demo("Blingy Notifications", R.layout.bling_notifications) )), DemoGroup("Materials", listOf( - Demo("Glass", GlassActivity::class) + Demo("Glass", GlassActivity::class), + Demo("Background Blur", BackgroundBlurActivity::class) )) ) @@ -126,4 +128,4 @@ class Main : Activity() { AllDemos.forEachIndexed { index, _ -> list.expandGroup(index) } } -}
\ No newline at end of file +} diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt new file mode 100644 index 000000000000..9d17d38d4298 --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt @@ -0,0 +1,189 @@ +/* + * 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.test.silkfx.materials + +import android.app.Activity +import android.content.Intent +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.PaintDrawable +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.provider.Settings +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import android.widget.ImageView +import android.widget.SeekBar +import android.widget.Switch +import android.widget.TextView +import com.android.test.silkfx.R +import com.android.internal.graphics.drawable.BackgroundBlurDrawable +import android.widget.LinearLayout +import android.widget.Button + +import android.view.ViewRootImpl + +class BackgroundBlurActivity : Activity(), SeekBar.OnSeekBarChangeListener { + var mBackgroundDrawable = PaintDrawable(Color.WHITE) + var mBackgroundBlurRadius = 50 + var mAlphaWithBlur = 0.2f + var mAlphaNoBlur = 0.5f + + var mBlurBehindRadius = 10 + var mDimAmountWithBlur = 0.2f + var mDimAmountNoBlur = 0.2f + + var mBlurForceDisabled = false + var mBatterySavingModeOn = false + + lateinit var blurBackgroundSeekBar: SeekBar + lateinit var backgroundAlphaSeekBar : SeekBar + lateinit var blurBehindSeekBar : SeekBar + lateinit var dimAmountSeekBar : SeekBar + + val blurEnabledListener = { enabled : Boolean -> + blurBackgroundSeekBar.setProgress(mBackgroundBlurRadius) + blurBehindSeekBar.setProgress(mBlurBehindRadius) + + if (enabled) { + setBackgroundBlur(mBackgroundBlurRadius) + setBackgroundColorAlpha(mAlphaWithBlur) + + setBlurBehind(mBlurBehindRadius) + setDimAmount(mDimAmountWithBlur) + + backgroundAlphaSeekBar.setProgress((mAlphaWithBlur * 100).toInt()) + dimAmountSeekBar.setProgress((mDimAmountWithBlur * 100).toInt()) + } else { + setBackgroundColorAlpha(mAlphaNoBlur) + setDimAmount(mDimAmountNoBlur) + + backgroundAlphaSeekBar.setProgress((mAlphaNoBlur * 100).toInt()) + dimAmountSeekBar.setProgress((mDimAmountNoBlur * 100).toInt()) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_background_blur) + + window.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND) + window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + + mBackgroundDrawable.setCornerRadius(30f) + window.setBackgroundDrawable(mBackgroundDrawable) + + mBatterySavingModeOn = + Settings.Global.getInt(getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) == 1 + setBatterySavingModeOn(mBatterySavingModeOn) + + blurBackgroundSeekBar = requireViewById(R.id.set_background_blur) + backgroundAlphaSeekBar = requireViewById(R.id.set_background_alpha) + blurBehindSeekBar = requireViewById(R.id.set_blur_behind) + dimAmountSeekBar = requireViewById(R.id.set_dim_amount) + + arrayOf(blurBackgroundSeekBar, backgroundAlphaSeekBar, blurBehindSeekBar, dimAmountSeekBar) + .forEach { + it.setOnSeekBarChangeListener(this) + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + getWindowManager().addCrossWindowBlurEnabledListener(blurEnabledListener) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + getWindowManager().removeCrossWindowBlurEnabledListener(blurEnabledListener) + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + when (seekBar) { + blurBackgroundSeekBar -> setBackgroundBlur(progress) + backgroundAlphaSeekBar -> setBackgroundColorAlpha(progress / 100.0f) + blurBehindSeekBar -> setBlurBehind(progress) + dimAmountSeekBar -> setDimAmount(progress / 100.0f) + else -> throw IllegalArgumentException("Unknown seek bar") + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + fun setBlurDisabled(disabled: Boolean) { + mBlurForceDisabled = disabled + Settings.Global.putInt(getContentResolver(), Settings.Global.DISABLE_WINDOW_BLURS, + if (mBlurForceDisabled) 1 else 0) + (findViewById(R.id.toggle_blur_enabled) as Button) + .setText(if (mBlurForceDisabled) "Enable blurs" else "Disable blurs") + } + + fun toggleForceBlurDisabled(v: View) { + setBlurDisabled(!mBlurForceDisabled) + } + + fun setBackgroundBlur(radius: Int) { + mBackgroundBlurRadius = radius + (findViewById(R.id.background_blur_radius) as TextView).setText(radius.toString()) + window.setBackgroundBlurRadius(mBackgroundBlurRadius) + } + + fun setBlurBehind(radius: Int) { + mBlurBehindRadius = radius + (findViewById(R.id.blur_behind_radius) as TextView).setText(radius.toString()) + window.getAttributes().setBlurBehindRadius(mBlurBehindRadius) + window.setAttributes(window.getAttributes()) + } + + fun setDimAmount(amount: Float) { + if (getWindowManager().isCrossWindowBlurEnabled()) { + mDimAmountWithBlur = amount + } else { + mDimAmountNoBlur = amount + } + (findViewById(R.id.dim_amount) as TextView).setText("%.2f".format(amount)) + window.getAttributes().dimAmount = amount + window.setAttributes(window.getAttributes()) + } + + fun setBatterySavingModeOn(on: Boolean) { + mBatterySavingModeOn = on + Settings.Global.putInt(getContentResolver(), + Settings.Global.LOW_POWER_MODE, if (on) 1 else 0) + (findViewById(R.id.toggle_battery_saving_mode) as Button).setText( + if (on) "Exit low power mode" else "Enter low power mode") + } + + fun toggleBatterySavingMode(v: View) { + setBatterySavingModeOn(!mBatterySavingModeOn) + } + + fun setBackgroundColorAlpha(alpha: Float) { + if (getWindowManager().isCrossWindowBlurEnabled()) { + mAlphaWithBlur = alpha + } else { + mAlphaNoBlur = alpha + } + (findViewById(R.id.background_alpha) as TextView).setText("%.2f".format(alpha)) + mBackgroundDrawable.setAlpha((alpha * 255f).toInt()) + getWindowManager().updateViewLayout(window.getDecorView(), window.getAttributes()) + } +} diff --git a/tests/SoundTriggerTestApp/OWNERS b/tests/SoundTriggerTestApp/OWNERS index 816bc6bba639..9db19a37812b 100644 --- a/tests/SoundTriggerTestApp/OWNERS +++ b/tests/SoundTriggerTestApp/OWNERS @@ -1 +1,2 @@ include /core/java/android/media/soundtrigger/OWNERS +mdooley@google.com diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml index 2c6c8d7cae20..1381c0a43c30 100644 --- a/tests/SoundTriggerTestApp/res/layout/main.xml +++ b/tests/SoundTriggerTestApp/res/layout/main.xml @@ -73,6 +73,14 @@ android:text="@string/play_trigger" android:onClick="onPlayTriggerButtonClicked" android:padding="20dp" /> + + <Button + android:id="@+id/get_state_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/get_model_state" + android:onClick="onGetModelStateButtonClicked" + android:padding="20dp" /> </LinearLayout> <LinearLayout diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml index c48b64884c5e..adb0fcf8e185 100644 --- a/tests/SoundTriggerTestApp/res/values/strings.xml +++ b/tests/SoundTriggerTestApp/res/values/strings.xml @@ -22,6 +22,7 @@ <string name="start_recog">Start</string> <string name="stop_recog">Stop</string> <string name="play_trigger">Play Trigger Audio</string> + <string name="get_model_state">Get State</string> <string name="capture">Capture Audio</string> <string name="stop_capture">Stop Capturing Audio</string> <string name="play_capture">Play Captured Audio</string> diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java index c3c4cf556986..103d516e5967 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java @@ -97,13 +97,8 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe setVolumeControlStream(AudioManager.STREAM_MUSIC); - // Make sure that the service is started, so even if our activity goes down, we'll still - // have a request for it to run. - startService(new Intent(getBaseContext(), SoundTriggerTestService.class)); - - // Bind to SoundTriggerTestService. - Intent intent = new Intent(this, SoundTriggerTestService.class); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, + AUDIO_PERMISSIONS_REQUEST); } @Override @@ -257,14 +252,26 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe } } + public synchronized void onGetModelStateButtonClicked(View v) { + if (mService == null) { + Log.e(TAG, "Can't get model state: not bound to SoundTriggerTestService"); + } else { + mService.getModelState(mSelectedModelUuid); + } + } + public synchronized void onCaptureAudioCheckboxClicked(View v) { // See if we have the right permissions - if (!mService.hasMicrophonePermission()) { - requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, - AUDIO_PERMISSIONS_REQUEST); - return; + if (mService == null) { + Log.e(TAG, "Can't set capture audio: not bound to SoundTriggerTestService"); } else { - mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); + if (!mService.hasMicrophonePermission()) { + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, + AUDIO_PERMISSIONS_REQUEST); + return; + } else { + mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); + } } } @@ -275,8 +282,15 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { // Make sure that the check box is set to false. mCaptureAudioCheckBox.setChecked(false); + } else { + // After granted Record_Audio permission, start and bind the service. + // so we can run that sound trigger capability, + // even if our activity goes down, we'll still have a request for it to run. + startService(new Intent(getBaseContext(), SoundTriggerTestService.class)); + // Bind to SoundTriggerTestService. + Intent intent = new Intent(this, SoundTriggerTestService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } - mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); } } diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java index 380e29984c63..6d4ffcff7d45 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; +import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioManager; @@ -46,6 +48,7 @@ import java.util.Properties; import java.util.Random; import java.util.UUID; + public class SoundTriggerTestService extends Service { private static final String TAG = "SoundTriggerTestSrv"; private static final String INTENT_ACTION = "com.android.intent.action.MANAGE_SOUND_TRIGGER"; @@ -57,6 +60,8 @@ public class SoundTriggerTestService extends Service { private Random mRandom; private UserActivity mUserActivity; + private static int captureCount; + public interface UserActivity { void addModel(UUID modelUuid, String state); void setModelState(UUID modelUuid, String state); @@ -131,6 +136,8 @@ public class SoundTriggerTestService extends Service { } else if (command.equals("set_capture_timeout")) { setCaptureAudioTimeout(getModelUuidFromIntent(intent), intent.getIntExtra("timeout", 5000)); + } else if (command.equals("get_model_state")) { + getModelState(getModelUuidFromIntent(intent)); } else { Log.e(TAG, "Unknown command '" + command + "'"); } @@ -432,6 +439,17 @@ public class SoundTriggerTestService extends Service { return modelInfo != null && modelInfo.captureAudioTrack != null; } + public synchronized void getModelState(UUID modelUuid) { + ModelInfo modelInfo = mModelInfoMap.get(modelUuid); + if (modelInfo == null) { + postError("Could not find model for: " + modelUuid.toString()); + return; + } + int status = mSoundTriggerUtil.getModelState(modelUuid); + postMessage("GetModelState for: " + modelInfo.name + " returns: " + + status); + } + private void loadModelsInDataDir() { // Load all the models in the data dir. boolean loadedModel = false; @@ -527,18 +545,29 @@ public class SoundTriggerTestService extends Service { } } + private class CaptureAudioRecorder implements Runnable { private final ModelInfo mModelInfo; + + // EventPayload and RecognitionEvent are equivalant. Only one will be non-null. private final SoundTriggerDetector.EventPayload mEvent; + private final RecognitionEvent mRecognitionEvent; public CaptureAudioRecorder(ModelInfo modelInfo, SoundTriggerDetector.EventPayload event) { mModelInfo = modelInfo; mEvent = event; + mRecognitionEvent = null; + } + + public CaptureAudioRecorder(ModelInfo modelInfo, RecognitionEvent event) { + mModelInfo = modelInfo; + mEvent = null; + mRecognitionEvent = event; } @Override public void run() { - AudioFormat format = mEvent.getCaptureAudioFormat(); + AudioFormat format = getAudioFormat(); if (format == null) { postErrorToast("No audio format in recognition event."); return; @@ -600,18 +629,21 @@ public class SoundTriggerTestService extends Service { } audioRecord = new AudioRecord(attributes, format, bytesRequired, - mEvent.getCaptureSession()); + getCaptureSession()); byte[] buffer = new byte[bytesRequired]; // Create a file so we can save the output data there for analysis later. FileOutputStream fos = null; try { - fos = new FileOutputStream( new File( - getFilesDir() + File.separator - + mModelInfo.name.replace(' ', '_') - + "_capture_" + format.getChannelCount() + "ch_" - + format.getSampleRate() + "hz_" + encoding + ".pcm")); + File file = new File( + getFilesDir() + File.separator + + mModelInfo.name.replace(' ', '_') + + "_capture_" + format.getChannelCount() + "ch_" + + format.getSampleRate() + "hz_" + encoding + + "_" + (++captureCount) + ".pcm"); + Log.i(TAG, "Writing audio to: " + file); + fos = new FileOutputStream(file); } catch (IOException e) { Log.e(TAG, "Failed to open output for saving PCM data", e); postErrorToast("Failed to open output for saving PCM data: " @@ -635,6 +667,10 @@ public class SoundTriggerTestService extends Service { bytesRequired -= bytesRead; } audioRecord.stop(); + if (fos != null) { + fos.flush(); + fos.close(); + } } catch (Exception e) { Log.e(TAG, "Error recording trigger audio", e); postErrorToast("Error recording trigger audio: " + e.getMessage()); @@ -651,6 +687,26 @@ public class SoundTriggerTestService extends Service { setModelState(mModelInfo, "Recording finished"); } } + + private AudioFormat getAudioFormat() { + if (mEvent != null) { + return mEvent.getCaptureAudioFormat(); + } + if (mRecognitionEvent != null) { + return mRecognitionEvent.captureFormat; + } + return null; + } + + private int getCaptureSession() { + if (mEvent != null) { + return mEvent.getCaptureSession(); + } + if (mRecognitionEvent != null) { + return mRecognitionEvent.captureSession; + } + return 0; + } } // Implementation of SoundTriggerDetector.Callback. diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java index cfe8c855ac81..996a78f2e42a 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java @@ -18,6 +18,8 @@ package com.android.test.soundtrigger; import android.annotation.Nullable; import android.content.Context; +import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; +import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; import android.media.soundtrigger.SoundTriggerDetector; import android.media.soundtrigger.SoundTriggerManager; import android.os.RemoteException; @@ -27,6 +29,7 @@ import android.util.Log; import com.android.internal.app.ISoundTriggerService; +import java.lang.reflect.Method; import java.lang.RuntimeException; import java.util.UUID; @@ -50,13 +53,31 @@ public class SoundTriggerUtil { * The sound model must contain a valid UUID. * * @param soundModel The sound model to add/update. + * @return The true if the model was loaded successfully, false otherwise. */ public boolean addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) { if (soundModel == null) { throw new RuntimeException("Bad sound model"); } mSoundTriggerManager.updateModel(soundModel); - return true; + // TODO: call loadSoundModel in the soundtrigger manager updateModel method + // instead of here. It is needed to keep soundtrigger manager internal + // state consistent. + return mSoundTriggerManager + .loadSoundModel(getGenericSoundModel(soundModel)) == 0; + } + + private GenericSoundModel getGenericSoundModel( + SoundTriggerManager.Model soundModel) { + try { + Method method = SoundTriggerManager.Model.class + .getDeclaredMethod("getGenericSoundModel"); + method.setAccessible(true); + return (GenericSoundModel) method.invoke(soundModel); + } catch (ReflectiveOperationException e) { + Log.e(TAG, "Failed to getGenericSoundModel: " + soundModel, e); + return null; + } } /** @@ -92,6 +113,16 @@ public class SoundTriggerUtil { return true; } + /** + * Get the current model state + * + * @param modelId The model ID to look-up the sound model for. + * @return 0 if the call succeeds, or an error code if it fails. + */ + public int getModelState(UUID modelId) { + return mSoundTriggerManager.getModelState(modelId); + } + public SoundTriggerDetector createSoundTriggerDetector(UUID modelId, SoundTriggerDetector.Callback callback) { return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null); diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index cac14a72a706..cce0dde9e6b9 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -31,12 +31,14 @@ android_test_helper_app { ], test_suites: ["general-tests"], java_resources: [ + ":apex.apexd_test_classpath", ":com.android.apex.apkrollback.test_v2", ":StagedInstallTestApexV2", ":StagedInstallTestApexV2_WrongSha", ":test.rebootless_apex_v1", ":test.rebootless_apex_v2", ], + platform_apis: true, } java_test_host { @@ -53,11 +55,14 @@ java_test_host { "cts-install-lib-host", ], data: [ + ":apex.apexd_test", ":com.android.apex.apkrollback.test_v1", - ":com.android.apex.cts.shim.v2_prebuilt", + ":StagedInstallTestApexV2", ":StagedInstallTestApexV2_WrongSha", ":TestAppAv1", ":test.rebootless_apex_v1", + ":test.rebootless_apex_v2", + ":test_com.android.server", ], test_suites: ["general-tests"], test_config: "StagedInstallInternalTest.xml", diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index 4684f0182d03..7490d3f2b50c 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -22,23 +22,37 @@ import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + import android.Manifest; +import android.content.pm.ApexStagedEvent; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManagerNative; +import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.content.pm.StagedApexInfo; +import android.os.IBinder; +import android.os.ServiceManager; import androidx.test.platform.app.InstrumentationRegistry; import com.android.cts.install.lib.Install; import com.android.cts.install.lib.InstallUtils; import com.android.cts.install.lib.TestApp; +import com.android.cts.install.lib.Uninstall; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -61,6 +75,13 @@ public class StagedInstallInternalTest { "ApexV2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, "com.android.apex.cts.shim.v2.apex"); + private static final String TEST_APEX_PACKAGE_NAME = "com.android.apex.test_package"; + private static final TestApp TEST_APEX_CLASSPATH = new TestApp("TestApex", + TEST_APEX_PACKAGE_NAME, 1, /*isApex=*/true, + "apex.apexd_test_classpath.apex"); + + private static final String TEST_APEX_SYSTEM_SERVER_PACKAGE_NAME = "test_com.android.server"; + private File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), "stagedinstall_state"); @@ -90,6 +111,7 @@ public class StagedInstallInternalTest { @Test public void cleanUp() throws Exception { Files.deleteIfExists(mTestStateFile.toPath()); + Uninstall.packages(TestApp.A, TestApp.B); } @Test @@ -139,8 +161,18 @@ public class StagedInstallInternalTest { @Test public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception { + // APEX verification InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed", Install.single(APEX_WRONG_SHA_V2).setStaged()); + InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed", + Install.multi(APEX_WRONG_SHA_V2, TestApp.A1).setStaged()); + // APK verification + Install.single(TestApp.A2).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.commitExpectingFailure(AssertionError.class, "Downgrade detected", + Install.single(TestApp.A1).setStaged()); + InstallUtils.commitExpectingFailure(AssertionError.class, "Downgrade detected", + Install.multi(TestApp.A1, TestApp.B1).setStaged()); } @Test @@ -158,6 +190,12 @@ public class StagedInstallInternalTest { } @Test + public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit() throws Exception { + int sessionId = Install.multi(TestApp.A1, TestApp.Apex2).setStaged().commit(); + storeSessionId(sessionId); + } + + @Test public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK", Install.single(TestApp.AIncompleteSplit).setStaged()); @@ -267,11 +305,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); } @Test @@ -279,11 +320,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false)); } @Test @@ -291,11 +335,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); } @Test @@ -303,11 +350,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false)); } @Test @@ -401,9 +451,93 @@ public class StagedInstallInternalTest { AssertionError.class, "Staged session " + sessionId + " already contains " + SHIM_APEX_PACKAGE_NAME, Install.single(APEX_V2)); + } + + @Test + public void testGetStagedModuleNames() throws Exception { + // Before staging a session + String[] result = getPackageManagerNative().getStagedApexModuleNames(); + assertThat(result).hasLength(0); + // Stage an apex + int sessionId = Install.single(APEX_V2).setStaged().commit(); + result = getPackageManagerNative().getStagedApexModuleNames(); + assertThat(result).hasLength(1); + assertThat(result).isEqualTo(new String[]{SHIM_APEX_PACKAGE_NAME}); + // Abandon the session + InstallUtils.openPackageInstallerSession(sessionId).abandon(); + result = getPackageManagerNative().getStagedApexModuleNames(); + assertThat(result).hasLength(0); + } + @Test + public void testGetStagedApexInfo() throws Exception { + // Ask for non-existing module + StagedApexInfo result = getPackageManagerNative().getStagedApexInfo("not found"); + assertThat(result).isNull(); + // Stage an apex + int sessionId = Install.single(TEST_APEX_CLASSPATH).setStaged().commit(); + // Query proper module name + result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME); + assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME); + assertThat(result.hasClassPathJars).isTrue(); + InstallUtils.openPackageInstallerSession(sessionId).abandon(); } + @Test + public void testGetAppInfo_flagTestOnlyIsSet() throws Exception { + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); + final ApplicationInfo info = pm.getApplicationInfo(TEST_APEX_SYSTEM_SERVER_PACKAGE_NAME, + ApplicationInfoFlags.of(PackageManager.MATCH_APEX)); + assertThat(info).isNotNull(); + assertThat((info.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0).isTrue(); + } + + public static class MockStagedApexObserver extends IStagedApexObserver.Stub { + @Override + public void onApexStaged(ApexStagedEvent event) { + assertThat(event).isNotNull(); + } + } + + @Test + public void testStagedApexObserver() throws Exception { + MockStagedApexObserver realObserver = new MockStagedApexObserver(); + IStagedApexObserver observer = spy(realObserver); + assertThat(observer).isNotNull(); + getPackageManagerNative().registerStagedApexObserver(observer); + + // Stage an apex and verify observer was called + int sessionId = Install.single(APEX_V2).setStaged().commit(); + ArgumentCaptor<ApexStagedEvent> captor = ArgumentCaptor.forClass(ApexStagedEvent.class); + verify(observer, timeout(5000)).onApexStaged(captor.capture()); + assertThat(captor.getValue().stagedApexModuleNames).isEqualTo( + new String[] {SHIM_APEX_PACKAGE_NAME}); + + // Abandon and verify observer is called + Mockito.clearInvocations(observer); + InstallUtils.openPackageInstallerSession(sessionId).abandon(); + verify(observer, timeout(5000)).onApexStaged(captor.capture()); + assertThat(captor.getValue().stagedApexModuleNames).hasLength(0); + } + + @Test + public void testRebootlessDowngrade() throws Exception { + final String packageName = "test.apex.rebootless"; + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(2); + TestApp apex1 = new TestApp("TestRebootlessApexV1", packageName, 1, + /* isApex= */ true, "test.rebootless_apex_v1.apex"); + InstallUtils.commitExpectingFailure(AssertionError.class, + "INSTALL_FAILED_VERSION_DOWNGRADE", Install.single(apex1)); + Install.single(apex1).setRequestDowngrade().commit(); + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); + } + + private IPackageManagerNative getPackageManagerNative() { + IBinder binder = ServiceManager.waitForService("package_native"); + assertThat(binder).isNotNull(); + return IPackageManagerNative.Stub.asInterface(binder); + } private static void assertSessionApplied(int sessionId) { assertSessionState(sessionId, (session) -> { assertThat(session.isStagedSessionApplied()).isTrue(); diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index 5021009f65ae..f60b4d6aad1e 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -61,6 +61,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { private static final String APEX_WRONG_SHA = "com.android.apex.cts.shim.v2_wrong_sha.apex"; private static final String APK_A = "TestAppAv1.apk"; private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String APEXD_TEST_APEX = "apex.apexd_test.apex"; + private static final String FAKE_APEX_SYSTEM_SERVER_APEX = "test_com.android.server.apex"; private static final String TEST_VENDOR_APEX_ALLOW_LIST = "/vendor/etc/sysconfig/test-vendor-apex-allow-list.xml"; @@ -91,7 +93,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex", - "/system/apex/test.rebootless_apex_v1.apex", + "/system/apex/test.rebootless_apex_v*.apex", "/data/apex/active/test.apex.rebootless*.apex", TEST_VENDOR_APEX_ALLOW_LIST); } @@ -111,6 +113,10 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { * @param files the paths of files which might contain wildcards */ private void deleteFiles(String... files) throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + boolean found = false; for (String file : files) { CommandResult result = getDevice().executeShellV2Command("ls " + file); @@ -121,9 +127,6 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } if (found) { - if (!getDevice().isAdbRoot()) { - getDevice().enableAdbRoot(); - } getDevice().remountSystemWritable(); for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); @@ -300,6 +303,18 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } @Test + @LargeTest + public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage() throws Exception { + List<String> before = getStagingDirectories(); + runPhase("testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit"); + assertThat(getStagingDirectories()).isNotEqualTo(before); + getDevice().reboot(); + runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify"); + List<String> after = getStagingDirectories(); + assertThat(after).isEqualTo(before); + } + + @Test public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { List<String> before = getStagingDirectories(); runPhase("testStagedInstallationShouldCleanUpOnValidationFailure"); @@ -478,6 +493,53 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testRebootlessUpdate_hasStagedSessionWithSameApex_fails"); } + @Test + public void testGetStagedModuleNames() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + runPhase("testGetStagedModuleNames"); + } + + @Test + @LargeTest + public void testGetStagedApexInfo() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestApex(APEXD_TEST_APEX); + getDevice().reboot(); + + runPhase("testGetStagedApexInfo"); + } + + @Test + @LargeTest + public void testGetAppInfo_flagTestOnlyIsSet() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestApex(FAKE_APEX_SYSTEM_SERVER_APEX); + getDevice().reboot(); + + runPhase("testGetAppInfo_flagTestOnlyIsSet"); + } + + @Test + public void testStagedApexObserver() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + runPhase("testStagedApexObserver"); + } + + @Test + public void testRebootlessDowngrade() throws Exception { + pushTestApex("test.rebootless_apex_v2.apex"); + getDevice().reboot(); + runPhase("testRebootlessDowngrade"); + } + private List<String> getStagingDirectories() throws DeviceNotAvailableException { String baseDir = "/data/app-staging"; try { diff --git a/tests/SurfaceViewSyncTest/Android.bp b/tests/SurfaceViewSyncTest/Android.bp new file mode 100644 index 000000000000..1c6e380aad63 --- /dev/null +++ b/tests/SurfaceViewSyncTest/Android.bp @@ -0,0 +1,31 @@ +// +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "SurfaceViewSyncTest", + srcs: ["**/*.java"], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/SurfaceViewSyncTest/AndroidManifest.xml b/tests/SurfaceViewSyncTest/AndroidManifest.xml new file mode 100644 index 000000000000..d085f8c168d3 --- /dev/null +++ b/tests/SurfaceViewSyncTest/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?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="com.android.test"> + <application> + <activity android:name="SurfaceViewSyncActivity" + android:label="SurfaceView Sync Test" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/SurfaceViewSyncTest/OWNERS b/tests/SurfaceViewSyncTest/OWNERS new file mode 100644 index 000000000000..0862c05e0ee4 --- /dev/null +++ b/tests/SurfaceViewSyncTest/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/wm/OWNERS diff --git a/tests/SurfaceViewSyncTest/res/layout/activity_surfaceview_sync.xml b/tests/SurfaceViewSyncTest/res/layout/activity_surfaceview_sync.xml new file mode 100644 index 000000000000..4433b2148384 --- /dev/null +++ b/tests/SurfaceViewSyncTest/res/layout/activity_surfaceview_sync.xml @@ -0,0 +1,47 @@ +<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/darker_gray"
+ tools:context="com.example.mysurfaceview.MainActivity">
+
+ <SurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="600dp" />
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <Button
+ android:text="COLLAPSE SV"
+ android:id="@+id/expand_sv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <Switch
+ android:id="@+id/enable_sync_switch"
+ android:text="Enable Sync"
+ android:checked="true"
+ android:layout_alignParentEnd="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file diff --git a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java new file mode 100644 index 000000000000..ab7f24a8d326 --- /dev/null +++ b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java @@ -0,0 +1,198 @@ +/* + * 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.test; + +import android.annotation.NonNull; +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowMetrics; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.Switch; +import android.window.SurfaceSyncer; + +/** + * Test app that allows the user to resize the SurfaceView and have the new buffer sync with the + * main window. This tests that {@link SurfaceSyncer} is working correctly. + */ +public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.Callback { + private static final String TAG = "SurfaceViewSyncActivity"; + + private SurfaceView mSurfaceView; + private boolean mLastExpanded = true; + + private RenderingThread mRenderingThread; + + private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer(); + + private Button mExpandButton; + private Switch mEnableSyncSwitch; + + private int mLastSyncId = -1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_surfaceview_sync); + mSurfaceView = findViewById(R.id.surface_view); + mSurfaceView.getHolder().addCallback(this); + + WindowManager windowManager = getWindowManager(); + WindowMetrics metrics = windowManager.getCurrentWindowMetrics(); + Rect bounds = metrics.getBounds(); + + LinearLayout container = findViewById(R.id.container); + mExpandButton = findViewById(R.id.expand_sv); + mEnableSyncSwitch = findViewById(R.id.enable_sync_switch); + mExpandButton.setOnClickListener(view -> updateSurfaceViewSize(bounds, container)); + + mRenderingThread = new RenderingThread(mSurfaceView.getHolder()); + } + + private void updateSurfaceViewSize(Rect bounds, View container) { + if (mLastSyncId >= 0) { + return; + } + + final float height; + if (mLastExpanded) { + height = bounds.height() / 2f; + mExpandButton.setText("EXPAND SV"); + } else { + height = bounds.height() / 1.5f; + mExpandButton.setText("COLLAPSE SV"); + } + mLastExpanded = !mLastExpanded; + + if (mEnableSyncSwitch.isChecked()) { + mLastSyncId = mSurfaceSyncer.setupSync(() -> { }); + mSurfaceSyncer.addToSync(mLastSyncId, container); + } + + ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams(); + svParams.height = (int) height; + mSurfaceView.setLayoutParams(svParams); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + final Canvas canvas = holder.lockCanvas(); + canvas.drawARGB(255, 255, 0, 0); + holder.unlockCanvasAndPost(canvas); + mRenderingThread.startRendering(); + mRenderingThread.renderFrame(null, mSurfaceView.getWidth(), mSurfaceView.getHeight()); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { + if (mEnableSyncSwitch.isChecked()) { + if (mLastSyncId < 0) { + mRenderingThread.renderFrame(null, width, height); + return; + } + mSurfaceSyncer.addToSync(mLastSyncId, mSurfaceView, frameCallback -> + mRenderingThread.renderFrame(frameCallback, width, height)); + mSurfaceSyncer.markSyncReady(mLastSyncId); + mLastSyncId = -1; + } else { + mRenderingThread.renderFrame(null, width, height); + } + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + mRenderingThread.stopRendering(); + } + + private static class RenderingThread extends HandlerThread { + private final SurfaceHolder mSurfaceHolder; + private Handler mHandler; + private SurfaceSyncer.SurfaceViewFrameCallback mFrameCallback; + private final Point mSurfaceSize = new Point(); + + int mColorValue = 0; + int mColorDelta = 10; + private final Paint mPaint = new Paint(); + + RenderingThread(SurfaceHolder holder) { + super("RenderingThread"); + mSurfaceHolder = holder; + mPaint.setColor(Color.BLACK); + mPaint.setTextSize(100); + } + + public void renderFrame(SurfaceSyncer.SurfaceViewFrameCallback frameCallback, int width, + int height) { + if (mHandler != null) { + mHandler.post(() -> { + mFrameCallback = frameCallback; + mSurfaceSize.set(width, height); + mRunnable.run(); + }); + } + } + + private final Runnable mRunnable = new Runnable() { + @Override + public void run() { + if (mFrameCallback != null) { + mFrameCallback.onFrameStarted(); + } + + try { + // Long delay from start to finish to mimic slow draw + Thread.sleep(1000); + } catch (InterruptedException e) { + } + + mColorValue += mColorDelta; + if (mColorValue > 245 || mColorValue < 10) { + mColorDelta *= -1; + } + + Canvas c = mSurfaceHolder.lockCanvas(); + c.drawRGB(255, 0, 0); + c.drawText("RENDERED CONTENT", 0, mSurfaceSize.y / 2, mPaint); + mSurfaceHolder.unlockCanvasAndPost(c); + mFrameCallback = null; + } + }; + + public void startRendering() { + start(); + mHandler = new Handler(getLooper()); + } + + public void stopRendering() { + if (mHandler != null) { + mHandler.removeCallbacks(mRunnable); + } + } + } +} diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java index 7ee19fb37244..052ce3a902c1 100644 --- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java +++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; @@ -214,7 +215,7 @@ public class SmsApplicationTest { ApplicationInfo bluetoothApplicationInfo = new ApplicationInfo(); bluetoothApplicationInfo.uid = FAKE_BT_UID; bluetoothPackageInfo.applicationInfo = bluetoothApplicationInfo; - when(mPackageManager.getPackageInfo(eq(SmsApplication.BLUETOOTH_PACKAGE_NAME), anyInt())) + when(mPackageManager.getPackageInfo(matches(".*android.bluetooth.services"), anyInt())) .thenReturn(bluetoothPackageInfo); PackageInfo telephonyProviderPackageInfo = new PackageInfo(); diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp new file mode 100644 index 000000000000..77f98e88f1eb --- /dev/null +++ b/tests/TrustTests/Android.bp @@ -0,0 +1,41 @@ +// 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 { + name: "TrustTests", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.ext.junit", + "androidx.test.uiautomator", + "mockito-target-minus-junit4", + "servicestests-utils", + "truth-prebuilt", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + test_suites: [ + "device-tests", + ], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml new file mode 100644 index 000000000000..8b4cbfd0e44b --- /dev/null +++ b/tests/TrustTests/AndroidManifest.xml @@ -0,0 +1,87 @@ +<?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.trust.test" + android:targetSandboxVersion="2"> + + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> + <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" /> + <uses-permission android:name="android.permission.TRUST_LISTENER" /> + + <application> + <uses-library android:name="android.test.runner"/> + <activity android:name="android.trust.TrustTestActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + <service + android:name=".UserUnlockRequestTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + + <service + android:name=".LockUserTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + + <service + android:name=".GrantAndRevokeTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + + <service + android:name=".TemporaryAndRenewableTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.trust.test"> + </instrumentation> +</manifest> diff --git a/tests/TrustTests/AndroidTest.xml b/tests/TrustTests/AndroidTest.xml new file mode 100644 index 000000000000..7ed19f5ba5fe --- /dev/null +++ b/tests/TrustTests/AndroidTest.xml @@ -0,0 +1,35 @@ +<?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. + --> +<configuration description="TrustTests configuration"> + <option name="test-tag" value="TrustTests" /> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <!-- Disable the double-tap power button for camera --> + <option name="run-command" + value="settings put secure camera_double_tap_power_gesture_disabled 1" /> + <option name="teardown-command" + value="settings delete secure camera_double_tap_power_gesture_disabled" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="TrustTests.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.trust.test" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/TrustTests/OWNERS b/tests/TrustTests/OWNERS new file mode 100644 index 000000000000..e2c6ce15b51e --- /dev/null +++ b/tests/TrustTests/OWNERS @@ -0,0 +1 @@ +include /core/java/android/service/trust/OWNERS diff --git a/tests/TrustTests/README.md b/tests/TrustTests/README.md new file mode 100644 index 000000000000..3427e30536d9 --- /dev/null +++ b/tests/TrustTests/README.md @@ -0,0 +1,40 @@ +# TrustTests framework tests + +These tests test the "trust" part of the platform primarily implemented via TrustManagerService in +the system server and TrustAgentService in system apps. + +Tests are separated into separate files based on major groupings. When creating new tests, find a +_closely_ matching existing test file or create a new test file. Prefer many test files over large +test files. + +Each test file has its own trust agent. To create a new trust agent: + +1. Create a new class extending from `BaseTrustAgentService` class in your test file +2. Add a new `<service>` stanza to `AndroidManifest.xml` in this directory for the new agent + following the pattern fo the existing agents. + +To run: + +```atest TrustTests``` + +## Testing approach: + +1. Test the agent service as a black box; avoid inspecting internal state of the service or + modifying the system code outside of this directory. +2. The primary interface to the system is through these three points: + 1. `TrustAgentService`, your agent created by the `TrustAgentRule` and accessible via + the `agent` property of the rule. + 1. Call command methods (e.g. `grantTrust`) directly on the agent + 2. Listen to events (e.g. `onUserRequestedUnlock`) by implementing the method in + your test's agent class and tracking invocations. See `UserUnlockRequestTest` for an + example. + 2. `TrustManager` which is the interface the rest of the system (e.g. SystemUI) has to the + service. + 1. Through this API, simulate system events that the service cares about + (e.g. `reportUnlockAttempt`). + 3. `TrustListener` which is the interface the rest of the system (e.g. SystemUI) uses to receive + events from the service. + 1. Through this, verify behavior that affects the rest of the system. For example, + see `LockStateTrackingRule`. +3. To re-use code between tests, prefer creating new rules alongside the existing rules or adding + functionality to a _closely_ matching existing rule. diff --git a/tests/TrustTests/TEST_MAPPING b/tests/TrustTests/TEST_MAPPING new file mode 100644 index 000000000000..23923eeb83ee --- /dev/null +++ b/tests/TrustTests/TEST_MAPPING @@ -0,0 +1,28 @@ +{ + "presubmit": [ + { + "name": "TrustTests", + "options": [ + { + "include-filter": "android.trust.test" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ], + "trust-tablet": [ + { + "name": "TrustTests", + "options": [ + { + "include-filter": "android.trust.test" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt new file mode 100644 index 000000000000..cf4965f1655d --- /dev/null +++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt @@ -0,0 +1,47 @@ +/* + * 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.trust + +import android.service.trust.TrustAgentService +import android.util.Log +import kotlin.reflect.KClass + +/** + * Base class for test trust agents. + */ +abstract class BaseTrustAgentService : TrustAgentService() { + + override fun onCreate() { + super.onCreate() + Log.d(TAG, "${this::class.simpleName} created") + instances[this::class] = this + } + + override fun onDestroy() { + super.onDestroy() + instances.remove(this::class) + } + + companion object { + private val instances = + mutableMapOf<KClass<out BaseTrustAgentService>, BaseTrustAgentService>() + private const val TAG = "BaseTrustAgentService" + + fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? { + return instances[serviceClass] + } + } +} diff --git a/tests/TrustTests/src/android/trust/TrustTestActivity.kt b/tests/TrustTests/src/android/trust/TrustTestActivity.kt new file mode 100644 index 000000000000..6c56feace3d7 --- /dev/null +++ b/tests/TrustTests/src/android/trust/TrustTestActivity.kt @@ -0,0 +1,30 @@ +/* + * 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.trust + +import android.app.Activity +import android.os.Bundle + +/** + * Activity for testing Trust. + */ +class TrustTestActivity : Activity() { + + public override fun onCreate(icicle: Bundle?) { + super.onCreate(icicle) + setTurnScreenOn(true) + } +} diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt new file mode 100644 index 000000000000..f864fedf4e62 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt @@ -0,0 +1,104 @@ +/* + * 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.trust.test + +import android.service.trust.GrantTrustResult +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import com.android.server.testutils.mock +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.mockito.Mockito.verifyZeroInteractions + +/** + * Test for testing revokeTrust & grantTrust for non-renewable trust. + * + * atest TrustTests:GrantAndRevokeTrustTest + */ +@RunWith(AndroidJUnit4::class) +class GrantAndRevokeTrustTest { + private val uiDevice = UiDevice.getInstance(getInstrumentation()) + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Before + fun manageTrust() { + trustAgentRule.agent.setManagingTrust(true) + } + + // This test serves a baseline for Grant tests, verifying that the default behavior of the + // device is to lock when put to sleep + @Test + fun sleepingDeviceWithoutGrantLocksDevice() { + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantKeepsDeviceUnlocked() { + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {} + uiDevice.sleep() + + lockStateTrackingRule.assertUnlocked() + } + + @Test + fun grantKeepsDeviceUnlocked_untilRevoked() { + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) {} + await() + uiDevice.sleep() + trustAgentRule.agent.revokeTrust() + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantDoesNotCallBack() { + val callback = mock<(GrantTrustResult) -> Unit>() + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback) + await() + + verifyZeroInteractions(callback) + } + + companion object { + private const val TAG = "GrantAndRevokeTrustTest" + private const val GRANT_MESSAGE = "granted by test" + private fun await() = Thread.sleep(250) + } +} + +class GrantAndRevokeTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt new file mode 100644 index 000000000000..1194afa0123f --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt @@ -0,0 +1,63 @@ +/* + * 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.trust.test + +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import android.util.Log +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing lockUser. + * + * atest TrustTests:LockUserTest + */ +@RunWith(AndroidJUnit4::class) +class LockUserTest { + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule<LockUserTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Test + fun lockUser_locksTheDevice() { + Log.i(TAG, "Locking user") + trustAgentRule.agent.lockUser() + + lockStateTrackingRule.assertLocked() + } + + companion object { + private const val TAG = "LockUserTest" + } +} + +class LockUserTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt new file mode 100644 index 000000000000..ae722477a2bc --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt @@ -0,0 +1,158 @@ +/* + * 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.trust.test + +import android.service.trust.GrantTrustResult +import android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT +import android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import android.util.Log +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import android.trust.test.lib.wait +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing revokeTrust & grantTrust for renewable trust. + * + * atest TrustTests:TemporaryAndRenewableTrustTest + */ +@RunWith(AndroidJUnit4::class) +class TemporaryAndRenewableTrustTest { + private val uiDevice = UiDevice.getInstance(getInstrumentation()) + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule<TemporaryAndRenewableTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Before + fun manageTrust() { + trustAgentRule.agent.setManagingTrust(true) + } + + // This test serves a baseline for Grant tests, verifying that the default behavior of the + // device is to lock when put to sleep + @Test + fun sleepingDeviceWithoutGrantLocksDevice() { + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantTrustLockedDevice_deviceStaysLocked() { + uiDevice.sleep() + lockStateTrackingRule.assertLocked() + + uiDevice.wakeUp() + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantTrustUnlockedDevice_deviceLocksOnScreenOff() { + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantTrustLockedDevice_grantTrustOnLockedDeviceUnlocksDevice() { + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + + uiDevice.wakeUp() + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + + lockStateTrackingRule.assertUnlocked() + } + + @Test + fun grantTrustLockedDevice_callsBackWhenUnlocked() { + Log.i(TAG, "Granting renewable trust while unlocked") + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + await(1000) + + Log.i(TAG, "Locking device") + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + uiDevice.wakeUp() + + Log.i(TAG, "Renewing trust and unlocking") + var result: GrantTrustResult? = null + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) { + Log.i(TAG, "Callback received; status=${it.status}") + result = it + } + lockStateTrackingRule.assertUnlocked() + + wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT } + } + + @Test + fun grantTrustLockedDevice_revokeTrustPreventsSubsequentUnlock() { + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + + trustAgentRule.agent.revokeTrust() + await(500) + uiDevice.wakeUp() + + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + + lockStateTrackingRule.assertLocked() + } + + companion object { + private const val TAG = "TemporaryAndRenewableTrustTest" + private const val GRANT_MESSAGE = "granted by test" + private fun await(millis: Long) = Thread.sleep(millis) + } +} + +class TemporaryAndRenewableTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt new file mode 100644 index 000000000000..6a8752abfde7 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -0,0 +1,110 @@ +/* + * 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.trust.test + +import android.app.trust.TrustManager +import android.content.Context +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import android.util.Log +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for the user unlock triggers. + * + * atest TrustTests:UserUnlockRequestTest + */ +@RunWith(AndroidJUnit4::class) +class UserUnlockRequestTest { + private val context = getApplicationContext<Context>() + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val userId = context.userId + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val trustAgentRule = TrustAgentRule<UserUnlockRequestTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(trustAgentRule) + + @Test + fun reportUserRequestedUnlock_propagatesToAgent() { + val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount + trustManager.reportUserRequestedUnlock(userId, false) + await() + + assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount) + .isEqualTo(oldCount + 1) + } + + @Test + fun reportUserRequestedUnlock_propagatesToAgentWithDismissKeyguard() { + trustManager.reportUserRequestedUnlock(userId, true) + await() + + assertThat(trustAgentRule.agent.lastCallDismissKeyguard) + .isTrue() + } + + @Test + fun reportUserMayRequestUnlock_propagatesToAgent() { + val oldCount = trustAgentRule.agent.onUserMayRequestUnlockCallCount + trustManager.reportUserMayRequestUnlock(userId) + await() + + assertThat(trustAgentRule.agent.onUserMayRequestUnlockCallCount) + .isEqualTo(oldCount + 1) + } + + companion object { + private const val TAG = "UserUnlockRequestTest" + private fun await() = Thread.sleep(250) + } +} + +class UserUnlockRequestTrustAgent : BaseTrustAgentService() { + var onUserRequestedUnlockCallCount: Long = 0 + private set + var onUserMayRequestUnlockCallCount: Long = 0 + private set + var lastCallDismissKeyguard: Boolean = false + private set + + override fun onUserRequestedUnlock(dismissKeyguard: Boolean) { + Log.i(TAG, "onUserRequestedUnlock($dismissKeyguard)") + onUserRequestedUnlockCallCount++ + lastCallDismissKeyguard = dismissKeyguard + } + + override fun onUserMayRequestUnlock() { + Log.i(TAG, "onUserMayRequestUnlock") + onUserMayRequestUnlockCallCount++ + } + + companion object { + private const val TAG = "UserUnlockRequestTrustAgent" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt new file mode 100644 index 000000000000..2031af2cf0c9 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -0,0 +1,88 @@ +/* + * 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.trust.test.lib + +import android.app.trust.TrustManager +import android.app.trust.TrustManager.TrustListener +import android.content.Context +import android.util.Log +import android.view.WindowManagerGlobal +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Rule for tracking the lock state of the device based on events emitted to [TrustListener]. + */ +class LockStateTrackingRule : TestRule { + private val context: Context = getApplicationContext() + private val windowManager = WindowManagerGlobal.getWindowManagerService() + + @Volatile lateinit var lockState: LockState + private set + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + lockState = LockState(locked = windowManager.isKeyguardLocked) + val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + val listener = Listener() + + trustManager.registerTrustListener(listener) + try { + base.evaluate() + } finally { + trustManager.unregisterTrustListener(listener) + } + } + } + + fun assertLocked() { + wait("un-locked per TrustListener") { lockState.locked == true } + wait("keyguard lock") { windowManager.isKeyguardLocked } + } + + fun assertUnlocked() { + wait("locked per TrustListener") { lockState.locked == false } + } + + inner class Listener : TrustListener { + override fun onTrustChanged( + enabled: Boolean, + userId: Int, + flags: Int, + trustGrantedMessages: MutableList<String> + ) { + Log.d(TAG, "Device became trusted=$enabled") + lockState = lockState.copy(locked = !enabled) + } + + override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { + } + + override fun onTrustError(message: CharSequence) { + } + } + + data class LockState( + val locked: Boolean? = null + ) + + companion object { + private const val TAG = "LockStateTrackingRule" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt new file mode 100644 index 000000000000..4189baae10cb --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -0,0 +1,125 @@ +/* + * 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.trust.test.lib + +import android.content.Context +import android.util.Log +import android.view.KeyEvent +import android.view.WindowManagerGlobal +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockscreenCredential +import com.google.common.truth.Truth.assertWithMessage +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Sets a screen lock on the device for the duration of the test. + */ +class ScreenLockRule : TestRule { + private val context: Context = getApplicationContext() + private val uiDevice = UiDevice.getInstance(getInstrumentation()) + private val windowManager = WindowManagerGlobal.getWindowManagerService() + private val lockPatternUtils = LockPatternUtils(context) + private var instantLockSavedValue = false + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + verifyNoScreenLockAlreadySet() + dismissKeyguard() + setScreenLock() + setLockOnPowerButton() + + try { + base.evaluate() + } finally { + removeScreenLock() + revertLockOnPowerButton() + dismissKeyguard() + } + } + } + + private fun verifyNoScreenLockAlreadySet() { + assertWithMessage("Screen Lock must not already be set on device") + .that(lockPatternUtils.isSecure(context.userId)) + .isFalse() + } + + fun dismissKeyguard() { + wait("keyguard dismissed") { count -> + if (!uiDevice.isScreenOn) { + Log.i(TAG, "Waking device, +500ms") + uiDevice.wakeUp() + } + + // Bouncer may be shown due to a race; back dismisses it + if (count >= 10) { + Log.i(TAG, "Pressing back to dismiss Bouncer") + uiDevice.pressKeyCode(KeyEvent.KEYCODE_BACK) + } + + windowManager.dismissKeyguard(null, null) + + !windowManager.isKeyguardLocked + } + } + + private fun setScreenLock() { + lockPatternUtils.setLockCredential( + LockscreenCredential.createPin(PIN), + LockscreenCredential.createNone(), + context.userId + ) + wait("screen lock set") { lockPatternUtils.isSecure(context.userId) } + Log.i(TAG, "Device PIN set to $PIN") + } + + private fun setLockOnPowerButton() { + instantLockSavedValue = lockPatternUtils.getPowerButtonInstantlyLocks(context.userId) + lockPatternUtils.setPowerButtonInstantlyLocks(true, context.userId) + } + + private fun removeScreenLock() { + var lockCredentialUnset = lockPatternUtils.setLockCredential( + LockscreenCredential.createNone(), + LockscreenCredential.createPin(PIN), + context.userId) + Log.i(TAG, "Removing screen lock") + assertWithMessage("Lock screen credential should be unset") + .that(lockCredentialUnset) + .isTrue() + + lockPatternUtils.setLockScreenDisabled(true, context.userId) + wait("screen lock un-set") { + lockPatternUtils.isLockScreenDisabled(context.userId) + } + wait("screen lock insecure") { !lockPatternUtils.isSecure(context.userId) } + } + + private fun revertLockOnPowerButton() { + lockPatternUtils.setPowerButtonInstantlyLocks(instantLockSavedValue, context.userId) + } + + companion object { + private const val TAG = "ScreenLockRule" + private const val PIN = "0000" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt new file mode 100644 index 000000000000..18bc029b6845 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt @@ -0,0 +1,110 @@ +/* + * 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.trust.test.lib + +import android.app.trust.TrustManager +import android.content.ComponentName +import android.content.Context +import android.trust.BaseTrustAgentService +import android.util.Log +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import com.android.internal.widget.LockPatternUtils +import com.google.common.truth.Truth.assertWithMessage +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import kotlin.reflect.KClass + +/** + * Enables a trust agent and causes the system service to bind to it. + * + * The enabled agent can be accessed during the test via the [agent] property. + * + * @constructor Creates the rule. Do not use; instead, use [invoke]. + */ +class TrustAgentRule<T : BaseTrustAgentService>( + private val serviceClass: KClass<T> +) : TestRule { + private val context: Context = getApplicationContext() + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val lockPatternUtils = LockPatternUtils(context) + + val agent get() = BaseTrustAgentService.instance(serviceClass) as T + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + verifyTrustServiceRunning() + unlockDeviceWithCredential() + enableTrustAgent() + + try { + verifyAgentIsRunning() + base.evaluate() + } finally { + disableTrustAgent() + } + } + } + + private fun verifyTrustServiceRunning() { + assertWithMessage("Trust service is not running").that(trustManager).isNotNull() + } + + private fun unlockDeviceWithCredential() { + Log.d(TAG, "Unlocking device with credential") + trustManager.reportUnlockAttempt(true, context.userId) + } + + private fun enableTrustAgent() { + val componentName = ComponentName(context, serviceClass.java) + val userId = context.userId + Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId") + val agents = mutableListOf(componentName) + .plus(lockPatternUtils.getEnabledTrustAgents(userId)) + .distinct() + lockPatternUtils.setEnabledTrustAgents(agents, userId) + } + + private fun verifyAgentIsRunning() { + wait("${serviceClass.simpleName} to be running") { + BaseTrustAgentService.instance(serviceClass) != null + } + } + + private fun disableTrustAgent() { + val componentName = ComponentName(context, serviceClass.java) + val userId = context.userId + Log.i(TAG, "Disabling trust agent ${componentName.flattenToString()} for user $userId") + val agents = lockPatternUtils.getEnabledTrustAgents(userId).toMutableList() + .distinct() + .minus(componentName) + lockPatternUtils.setEnabledTrustAgents(agents, userId) + } + + companion object { + /** + * Creates a new rule for the specified agent class. Example usage: + * ``` + * @get:Rule val rule = TrustAgentRule<MyTestAgent>() + * ``` + */ + inline operator fun <reified T : BaseTrustAgentService> invoke() = + TrustAgentRule(T::class) + + private const val TAG = "TrustAgentRule" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/utils.kt new file mode 100644 index 000000000000..e047202f6740 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/utils.kt @@ -0,0 +1,51 @@ +/* + * 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.trust.test.lib + +import android.util.Log +import com.google.common.truth.Truth.assertWithMessage + +private const val TAG = "TrustTestUtils" + +/** + * Waits for [conditionFunction] to be true with a failed assertion if it is not after [maxWait] + * ms. + * + * The condition function can perform additional logic (for example, logging or attempting to make + * the condition become true). + * + * @param conditionFunction function which takes the attempt count & returns whether the condition + * is met + */ +internal fun wait( + description: String? = null, + maxWait: Long = 30000L, + rate: Long = 50L, + conditionFunction: (count: Int) -> Boolean +) { + var waited = 0L + var count = 0 + while (!conditionFunction.invoke(count)) { + assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description") + .that(waited <= maxWait) + .isTrue() + waited += rate + count++ + Log.i(TAG, "Waiting for $description ($waited/$maxWait) #$count") + Thread.sleep(rate) + } +} diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index e07fbbf7a1c1..9a9e42bfc300 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -37,15 +37,19 @@ android_test { "vts", ], data: [ - ":NotoColorEmojiTtf", ":UpdatableSystemFontTestCertDer", - ":UpdatableSystemFontTestNotoColorEmojiTtfFsvSig", - ":UpdatableSystemFontTestNotoColorEmojiV0Ttf", - ":UpdatableSystemFontTestNotoColorEmojiV0TtfFsvSig", - ":UpdatableSystemFontTestNotoColorEmojiVPlus1Ttf", - ":UpdatableSystemFontTestNotoColorEmojiVPlus1TtfFsvSig", - ":UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf", - ":UpdatableSystemFontTestNotoColorEmojiVPlus2TtfFsvSig", + ":UpdatableSystemFontTest_NotoColorEmoji.ttf", + ":UpdatableSystemFontTest_NotoColorEmoji.sig", + ":UpdatableSystemFontTest_NotoColorEmojiV0.ttf", + ":UpdatableSystemFontTest_NotoColorEmojiV0.sig", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig", + ":UpdatableSystemFontTest_NotoSerif-Regular.ttf", + ":UpdatableSystemFontTest_NotoSerif-Regular.sig", + ":UpdatableSystemFontTest_NotoSerif-Bold.ttf", + ":UpdatableSystemFontTest_NotoSerif-Bold.sig", ], sdk_version: "test_current", } diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml index 4f6487e7e953..9e2a4b643bf6 100644 --- a/tests/UpdatableSystemFontTest/AndroidTest.xml +++ b/tests/UpdatableSystemFontTest/AndroidTest.xml @@ -28,14 +28,18 @@ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" /> - <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV0.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.ttf" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java index 947e9c2ff56a..a8c27fb0f116 100644 --- a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java +++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java @@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.app.Activity; +import android.graphics.Typeface; import android.os.Bundle; import android.widget.LinearLayout; import android.widget.TextView; @@ -27,14 +28,20 @@ import android.widget.TextView; /** Test app to render an emoji. */ public class EmojiRenderingTestActivity extends Activity { + private static final String TEST_NOTO_SERIF = "test-noto-serif"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout container = new LinearLayout(this); container.setOrientation(LinearLayout.VERTICAL); - TextView textView = new TextView(this); - textView.setText("\uD83E\uDD72"); // 🥲 - container.addView(textView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + TextView emojiTextView = new TextView(this); + emojiTextView.setText("\uD83E\uDD72"); // 🥲 + container.addView(emojiTextView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + TextView serifTextView = new TextView(this); + serifTextView.setTypeface(Typeface.create(TEST_NOTO_SERIF, Typeface.NORMAL)); + serifTextView.setText(TEST_NOTO_SERIF); + container.addView(serifTextView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); setContentView(container); } } diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index 6bd07d0a84fd..cbe13d9aa149 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -16,6 +16,9 @@ package com.android.updatablesystemfont; +import static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT; +import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; +import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static com.google.common.truth.Truth.assertThat; @@ -30,6 +33,7 @@ import android.content.Context; import android.graphics.fonts.FontFamilyUpdateRequest; import android.graphics.fonts.FontFileUpdateRequest; import android.graphics.fonts.FontManager; +import android.graphics.fonts.FontStyle; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.RootPermissionTest; import android.security.FileIntegrityManager; @@ -77,31 +81,45 @@ public class UpdatableSystemFontTest { private static final String SYSTEM_FONTS_DIR = "/system/fonts/"; private static final String DATA_FONTS_DIR = "/data/fonts/files/"; private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der"; - private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji"; - private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF = - "/data/local/tmp/NotoColorEmoji.ttf"; - private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"; + private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji"; + private static final String NOTO_COLOR_EMOJI_TTF = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.ttf"; + private static final String NOTO_COLOR_EMOJI_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig"; // A font with revision == 0. private static final String TEST_NOTO_COLOR_EMOJI_V0_TTF = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf"; - private static final String TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.ttf"; + private static final String TEST_NOTO_COLOR_EMOJI_V0_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.sig"; // A font with revision == original + 1 private static final String TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf"; - private static final String TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf"; + private static final String TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig"; // A font with revision == original + 2 private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf"; - private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf"; + private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig"; + + private static final String NOTO_SERIF_REGULAR_POSTSCRIPT_NAME = "NotoSerif"; + private static final String NOTO_SERIF_REGULAR_TTF = + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.ttf"; + private static final String NOTO_SERIF_REGULAR_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig"; + + private static final String NOTO_SERIF_BOLD_POSTSCRIPT_NAME = "NotoSerif-Bold"; + private static final String NOTO_SERIF_BOLD_TTF = + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.ttf"; + private static final String NOTO_SERIF_BOLD_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig"; private static final String EMOJI_RENDERING_TEST_APP_ID = "com.android.emojirenderingtestapp"; private static final String EMOJI_RENDERING_TEST_ACTIVITY = EMOJI_RENDERING_TEST_APP_ID + "/.EmojiRenderingTestActivity"; + // This should be the same as the one in EmojiRenderingTestActivity. + private static final String TEST_NOTO_SERIF = "test-noto-serif"; private static final long ACTIVITY_TIMEOUT_MILLIS = SECONDS.toMillis(10); private static final String GET_AVAILABLE_FONTS_TEST_ACTIVITY = @@ -141,11 +159,20 @@ public class UpdatableSystemFontTest { @Test public void updateFont() throws Exception { + FontConfig oldFontConfig = + SystemUtil.callWithShellPermissionIdentity(mFontManager::getFontConfig); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); + // Check that font config is updated. String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); + FontConfig newFontConfig = + SystemUtil.callWithShellPermissionIdentity(mFontManager::getFontConfig); + assertThat(newFontConfig.getConfigVersion()) + .isGreaterThan(oldFontConfig.getConfigVersion()); + assertThat(newFontConfig.getLastModifiedTimeMillis()) + .isGreaterThan(oldFontConfig.getLastModifiedTimeMillis()); // The updated font should be readable and unmodifiable. expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null"); expectCommandToFail("dd status=none if=" + CERT_PATH + " of=" + fontPath); @@ -154,11 +181,11 @@ public class UpdatableSystemFontTest { @Test public void updateFont_twice() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath2).startsWith(DATA_FONTS_DIR); @@ -173,16 +200,16 @@ public class UpdatableSystemFontTest { public void updateFont_allowSameVersion() throws Exception { // Update original font to the same version assertThat(updateFontFile( - ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG)) + NOTO_COLOR_EMOJI_TTF, NOTO_COLOR_EMOJI_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); // Update updated font to the same version assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); @@ -195,28 +222,58 @@ public class UpdatableSystemFontTest { @Test public void updateFont_invalidCert() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG)) .isEqualTo(FontManager.RESULT_ERROR_VERIFICATION_FAILURE); } @Test public void updateFont_downgradeFromSystem() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_SIG)) .isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); } @Test public void updateFont_downgradeFromData() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); } @Test + public void updateFontFamily() throws Exception { + assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS); + FontConfig.FontFamily family = findFontFamilyOrThrow("serif"); + assertThat(family.getFontList()).hasSize(2); + assertThat(family.getFontList().get(0).getPostScriptName()) + .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); + assertThat(family.getFontList().get(0).getFile().getAbsolutePath()) + .startsWith(DATA_FONTS_DIR); + assertThat(family.getFontList().get(0).getStyle().getWeight()) + .isEqualTo(FONT_WEIGHT_NORMAL); + assertThat(family.getFontList().get(1).getPostScriptName()) + .isEqualTo(NOTO_SERIF_BOLD_POSTSCRIPT_NAME); + assertThat(family.getFontList().get(1).getFile().getAbsolutePath()) + .startsWith(DATA_FONTS_DIR); + assertThat(family.getFontList().get(1).getStyle().getWeight()).isEqualTo(FONT_WEIGHT_BOLD); + } + + @Test + public void updateFontFamily_asNewFont() throws Exception { + assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif")) + .isEqualTo(FontManager.RESULT_SUCCESS); + FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + assertThat(family.getFontList()).hasSize(2); + assertThat(family.getFontList().get(0).getPostScriptName()) + .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); + assertThat(family.getFontList().get(1).getPostScriptName()) + .isEqualTo(NOTO_SERIF_BOLD_POSTSCRIPT_NAME); + } + + @Test public void launchApp() throws Exception { String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(SYSTEM_FONTS_DIR); @@ -231,22 +288,25 @@ public class UpdatableSystemFontTest { String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR); + updateNotoSerifAs(TEST_NOTO_SERIF); + String notoSerifPath = getFontPath(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); startActivity(EMOJI_RENDERING_TEST_APP_ID, EMOJI_RENDERING_TEST_ACTIVITY); // The original font should NOT be opened by the app. SystemUtil.eventually(() -> { assertThat(isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID)).isTrue(); assertThat(isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse(); + assertThat(isFileOpenedBy(notoSerifPath, EMOJI_RENDERING_TEST_APP_ID)).isTrue(); }, ACTIVITY_TIMEOUT_MILLIS); } @Test public void reboot() throws Exception { expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)); String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); @@ -264,7 +324,7 @@ public class UpdatableSystemFontTest { Pattern.compile(Pattern.quote(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF)); for (int i = 0; i < 10; i++) { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); List<String> openFiles = getOpenFiles("system_server"); for (Pattern p : Arrays.asList(PATTERN_FONT_FILES, PATTERN_SYSTEM_FONT_FILES, @@ -285,7 +345,7 @@ public class UpdatableSystemFontTest { public void fdLeakTest_withoutPermission() throws Exception { Pattern patternEmojiVPlus1 = Pattern.compile(Pattern.quote(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF)); - byte[] signature = Files.readAllBytes(Paths.get(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); + byte[] signature = Files.readAllBytes(Paths.get(TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)); try (ParcelFileDescriptor fd = ParcelFileDescriptor.open( new File(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF), MODE_READ_ONLY)) { assertThrows(SecurityException.class, @@ -340,18 +400,56 @@ public class UpdatableSystemFontTest { configVersion); } + private int updateNotoSerifAs(String familyName) throws IOException { + List<FontFamilyUpdateRequest.Font> fonts = Arrays.asList( + new FontFamilyUpdateRequest.Font.Builder(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME, + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT)).build(), + new FontFamilyUpdateRequest.Font.Builder(NOTO_SERIF_BOLD_POSTSCRIPT_NAME, + new FontStyle(FONT_WEIGHT_BOLD, FONT_SLANT_UPRIGHT)).build()); + FontFamilyUpdateRequest.FontFamily fontFamily = + new FontFamilyUpdateRequest.FontFamily.Builder(familyName, fonts).build(); + byte[] regularSig = Files.readAllBytes(Paths.get(NOTO_SERIF_REGULAR_SIG)); + byte[] boldSig = Files.readAllBytes(Paths.get(NOTO_SERIF_BOLD_SIG)); + try (ParcelFileDescriptor regularFd = ParcelFileDescriptor.open( + new File(NOTO_SERIF_REGULAR_TTF), MODE_READ_ONLY); + ParcelFileDescriptor boldFd = ParcelFileDescriptor.open( + new File(NOTO_SERIF_BOLD_TTF), MODE_READ_ONLY)) { + return SystemUtil.runWithShellPermissionIdentity(() -> { + FontConfig fontConfig = mFontManager.getFontConfig(); + return mFontManager.updateFontFamily(new FontFamilyUpdateRequest.Builder() + .addFontFileUpdateRequest( + new FontFileUpdateRequest(regularFd, regularSig)) + .addFontFileUpdateRequest( + new FontFileUpdateRequest(boldFd, boldSig)) + .addFontFamily(fontFamily) + .build(), fontConfig.getConfigVersion()); + }); + } + } + private String getFontPath(String psName) { - return SystemUtil.runWithShellPermissionIdentity(() -> { - FontConfig fontConfig = mFontManager.getFontConfig(); - for (FontConfig.FontFamily family : fontConfig.getFontFamilies()) { - for (FontConfig.Font font : family.getFontList()) { - if (psName.equals(font.getPostScriptName())) { - return font.getFile().getAbsolutePath(); - } - } - } - throw new AssertionError("Font not found: " + psName); - }); + FontConfig fontConfig = + SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); + return fontConfig.getFontFamilies().stream() + .flatMap(family -> family.getFontList().stream()) + .filter(font -> psName.equals(font.getPostScriptName())) + // Return the last match, because the latter family takes precedence if two families + // have the same name. + .reduce((first, second) -> second) + .orElseThrow(() -> new AssertionError("Font not found: " + psName)) + .getFile() + .getAbsolutePath(); + } + + private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) { + FontConfig fontConfig = + SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); + return fontConfig.getFontFamilies().stream() + .filter(family -> familyName.equals(family.getName())) + // Return the last match, because the latter family takes precedence if two families + // have the same name. + .reduce((first, second) -> second) + .orElseThrow(() -> new AssertionError("Family not found: " + familyName)); } private static void startActivity(String appId, String activityId) throws Exception { diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp index 426464ea574e..0bdb3a8c6b14 100644 --- a/tests/UpdatableSystemFontTest/testdata/Android.bp +++ b/tests/UpdatableSystemFontTest/testdata/Android.bp @@ -21,11 +21,19 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -// An existing module name is reused to avoid merge conflicts. -// TODO: fix the font and module name. filegroup { - name: "NotoColorEmojiTtf", - srcs: ["NotoColorEmoji.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmoji.ttf", + srcs: ["UpdatableSystemFontTest_NotoColorEmoji.ttf"], +} + +filegroup { + name: "UpdatableSystemFontTest_NotoSerif-Regular.ttf", + srcs: ["UpdatableSystemFontTest_NotoSerif-Regular.ttf"], +} + +filegroup { + name: "UpdatableSystemFontTest_NotoSerif-Bold.ttf", + srcs: ["UpdatableSystemFontTest_NotoSerif-Bold.ttf"], } filegroup { @@ -43,14 +51,10 @@ filegroup { srcs: ["UpdatableSystemFontTestCert.der"], } -genrule_defaults { - name: "updatable_system_font_increment_font_revision_default", -} - genrule { - name: "UpdatableSystemFontTestNotoColorEmojiV0Ttf", - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmojiV0.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmojiV0.ttf", + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiV0.ttf"], tools: ["update_font_metadata"], cmd: "$(location update_font_metadata) " + "--input=$(in) " + @@ -59,9 +63,9 @@ genrule { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus1Ttf", - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf", + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf"], tools: ["update_font_metadata"], cmd: "$(location update_font_metadata) " + "--input=$(in) " + @@ -70,9 +74,9 @@ genrule { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf", - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf", + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf"], tools: ["update_font_metadata"], cmd: "$(location update_font_metadata) " + "--input=$(in) " + @@ -94,29 +98,43 @@ genrule_defaults { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiTtfFsvSig", + name: "UpdatableSystemFontTest_NotoColorEmoji.sig", + defaults: ["updatable_system_font_sig_gen_default"], + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmoji.sig"], +} + +genrule { + name: "UpdatableSystemFontTest_NotoColorEmojiV0.sig", + defaults: ["updatable_system_font_sig_gen_default"], + srcs: [":UpdatableSystemFontTest_NotoColorEmojiV0.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiV0.sig"], +} + +genrule { + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"], + srcs: [":UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig"], } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiV0TtfFsvSig", + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":UpdatableSystemFontTestNotoColorEmojiV0Ttf"], - out: ["UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig"], + srcs: [":UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig"], } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus1TtfFsvSig", + name: "UpdatableSystemFontTest_NotoSerif-Regular.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":UpdatableSystemFontTestNotoColorEmojiVPlus1Ttf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig"], + srcs: ["UpdatableSystemFontTest_NotoSerif-Regular.ttf"], + out: ["UpdatableSystemFontTest_NotoSerif-Regular.sig"], } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus2TtfFsvSig", + name: "UpdatableSystemFontTest_NotoSerif-Bold.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig"], + srcs: ["UpdatableSystemFontTest_NotoSerif-Bold.ttf"], + out: ["UpdatableSystemFontTest_NotoSerif-Bold.sig"], } diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf Binary files differindex f71f52cbaa2b..f71f52cbaa2b 100644 --- a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx index 6540c5898ec5..6540c5898ec5 100644 --- a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf Binary files differnew file mode 100644 index 000000000000..66c1bd2a09b1 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx new file mode 100644 index 000000000000..8c4215eafc60 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx @@ -0,0 +1,196 @@ +<?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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="a"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <!-- Currently NotoSerif-Bold.ttf's fontRevision is 1.xx. + 100.0 will be sufficiently larger than that. --> + <fontRevision value="100.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Feb 16 12:00:00 2022"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="0x00010000"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="a" width="3000" lsb="93"/> <!-- 3em --> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <!-- length will be calculated by the compiler. --> + <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="0" language="0" nGroups="1"> + <!-- The font must support at least one of the characters used + in OtfFontFileParser to validate the font. --> + <map code="0x61" name="a" /> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="a" xMin="0" yMin="0" xMax="300" yMax="300"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="0" y="300" on="1" /> + <pt x="300" y="300" on="1" /> + <pt x="300" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + </glyf> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2022 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Bold + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + <!-- Android identifies the target font to be updated by PostScript name. + To test updating NotoSerif-Bold.ttf, the PostScript needs to be + the same as NotoSerif-Bold.ttf here. --> + NotoSerif-Bold + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf Binary files differnew file mode 100644 index 000000000000..707ae281d990 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx new file mode 100644 index 000000000000..754eae31c7c1 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx @@ -0,0 +1,196 @@ +<?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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="a"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <!-- Currently NotoSerif-Regular.ttf's fontRevision is 1.xx. + 100.0 will be sufficiently larger than that. --> + <fontRevision value="100.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Feb 16 12:00:00 2022"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="0x00010000"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="a" width="3000" lsb="93"/> <!-- 3em --> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <!-- length will be calculated by the compiler. --> + <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="0" language="0" nGroups="1"> + <!-- The font must support at least one of the characters used + in OtfFontFileParser to validate the font. --> + <map code="0x61" name="a" /> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="a" xMin="0" yMin="0" xMax="300" yMax="300"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="0" y="300" on="1" /> + <pt x="300" y="300" on="1" /> + <pt x="300" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + </glyf> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2022 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + <!-- Android identifies the target font to be updated by PostScript name. + To test updating NotoSerif-Regular.ttf, the PostScript needs to be + the same as NotoSerif-Regular.ttf here. --> + NotoSerif + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java index 7e8a13470c35..f695cbd5daf9 100644 --- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java +++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java @@ -62,12 +62,13 @@ public class UsageStatsDatabasePerfTest { private static final StatCombiner<UsageEvents.Event> sUsageStatsCombiner = new StatCombiner<UsageEvents.Event>() { @Override - public void combine(IntervalStats stats, boolean mutable, + public boolean combine(IntervalStats stats, boolean mutable, List<UsageEvents.Event> accResult) { final int size = stats.events.size(); for (int i = 0; i < size; i++) { accResult.add(stats.events.get(i)); } + return true; } }; diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java index e4880fd10d67..1341c85feec3 100644 --- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java +++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java @@ -62,7 +62,7 @@ public class TestEnrollmentActivity extends Activity { public void onEnrollButtonClicked(View v) { Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, Locale.forLanguageTag(BCP47_LOCALE), TEXT, - new int[] { UserManager.get(this).getUserHandle() /* current user */}); + new int[] { UserManager.get(this).getProcessUserId() /* current user */}); UUID modelUuid = UUID.randomUUID(); // Generate a fake model to push. byte[] data = new byte[1024]; diff --git a/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java b/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java deleted file mode 100644 index ef014f0d4e53..000000000000 --- a/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.net; - -import android.net.NetworkStats; -import android.os.SystemClock; -import com.android.server.net.NetworkStatsFactory; -import com.google.caliper.AfterExperiment; -import com.google.caliper.BeforeExperiment; -import java.io.File; - -public class NetworkStatsFactoryBenchmark { - private File mStats; - - // TODO: consider staging stats file with different number of rows - - @BeforeExperiment - protected void setUp() { - mStats = new File("/proc/net/xt_qtaguid/stats"); - } - - @AfterExperiment - protected void tearDown() { - mStats = null; - } - - public void timeReadNetworkStatsDetailJava(int reps) throws Exception { - for (int i = 0; i < reps; i++) { - NetworkStatsFactory.javaReadNetworkStatsDetail(mStats, NetworkStats.UID_ALL, - // Looks like this was broken by change d0c5b9abed60b7bc056d026bf0f2b2235410fb70 - // Fixed compilation problem but needs addressing properly. - new String[0], 999); - } - } - - public void timeReadNetworkStatsDetailNative(int reps) { - for (int i = 0; i < reps; i++) { - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - NetworkStatsFactory.nativeReadNetworkStatsDetail( - stats, mStats.getAbsolutePath(), NetworkStats.UID_ALL, - // Looks like this was broken by change d0c5b9abed60b7bc056d026bf0f2b2235410fb70 - // Fixed compilation problem but needs addressing properly. - new String[0], 999, false); - } - } -} diff --git a/tests/benchmarks/src/com/android/server/net/OWNERS b/tests/benchmarks/src/com/android/server/net/OWNERS deleted file mode 100644 index aa87958f1d53..000000000000 --- a/tests/benchmarks/src/com/android/server/net/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/net/OWNERS diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp new file mode 100644 index 000000000000..e5eb3c7b6394 --- /dev/null +++ b/tests/componentalias/Android.bp @@ -0,0 +1,87 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_defaults { + name: "ComponentAliasTests_defaults", + static_libs: [ + "androidx.test.rules", + "compatibility-device-util-axt", + "mockito-target-extended-minus-junit4", + "truth-prebuilt", + "ub-uiautomator", + ], + libs: ["android.test.base"], + srcs: [ + "src/**/*.java", + ], + test_suites: [ + "general-tests", + ], + platform_apis: true, // We use hidden APIs in the test. +} + +// We build three APKs from the exact same source files, so these APKs contain the exact same tests. +// And we run the tests on each APK, so that we can test various situations: +// - When the alias is in the same package, target in the same package. +// - When the alias is in the same package, target in another package. +// - When the alias is in another package, which also contains the target. +// - When the alias is in another package, and the target is in yet another package. +// etc etc... + +android_test { + name: "ComponentAliasTests", + defaults: [ + "ComponentAliasTests_defaults", + ], + package_name: "android.content.componentalias.tests", + manifest: "AndroidManifest.xml", + additional_manifests: [ + "AndroidManifest_main.xml", + "AndroidManifest_service_aliases.xml", + "AndroidManifest_service_targets.xml", + ], + test_config_template: "AndroidTest-template.xml", +} + +android_test { + name: "ComponentAliasTests1", + defaults: [ + "ComponentAliasTests_defaults", + ], + package_name: "android.content.componentalias.tests.sub1", + manifest: "AndroidManifest.xml", + additional_manifests: [ + "AndroidManifest_sub1.xml", + "AndroidManifest_service_targets.xml", + ], + test_config_template: "AndroidTest-template.xml", +} + +android_test { + name: "ComponentAliasTests2", + defaults: [ + "ComponentAliasTests_defaults", + ], + package_name: "android.content.componentalias.tests.sub2", + manifest: "AndroidManifest.xml", + additional_manifests: [ + "AndroidManifest_sub2.xml", + "AndroidManifest_service_targets.xml", + ], + test_config_template: "AndroidTest-template.xml", +} diff --git a/tests/componentalias/AndroidManifest.xml b/tests/componentalias/AndroidManifest.xml new file mode 100755 index 000000000000..7bb83a336833 --- /dev/null +++ b/tests/componentalias/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.componentalias.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" /> + </application> +</manifest> diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml new file mode 100755 index 000000000000..70e817ebf3e7 --- /dev/null +++ b/tests/componentalias/AndroidManifest_main.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.componentalias.tests" > + + <application> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.content.componentalias.tests" > + </instrumentation> +</manifest> diff --git a/tests/componentalias/AndroidManifest_service_aliases.xml b/tests/componentalias/AndroidManifest_service_aliases.xml new file mode 100644 index 000000000000..c96f1736c684 --- /dev/null +++ b/tests/componentalias/AndroidManifest_service_aliases.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.componentalias.tests" > + <application> + <!-- + Note the alias components are essentially just placeholders, so the APKs don't have to + have the implementation classes. + --> + <service android:name=".s.Alias00" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.s.Target00" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_00" /></intent-filter> + </service> + <service android:name=".s.Alias01" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target01" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_01" /></intent-filter> + </service> + <service android:name=".s.Alias02" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target02" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_02" /></intent-filter> + </service> + <service android:name=".s.Alias03" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target03" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_03" /></intent-filter> + </service> + <service android:name=".s.Alias04" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target04" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter> + </service> + + <receiver android:name=".b.Alias00" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.b.Target00" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Alias01" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target01" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Alias02" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target02" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Alias03" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target03" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Alias04" android:exported="true" android:enabled="true" > + <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target04" /> + <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + </application> +</manifest> diff --git a/tests/componentalias/AndroidManifest_service_targets.xml b/tests/componentalias/AndroidManifest_service_targets.xml new file mode 100644 index 000000000000..24c0432bcf4c --- /dev/null +++ b/tests/componentalias/AndroidManifest_service_targets.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.componentalias.tests" > + <application> + <service android:name=".s.Target00" android:exported="true" android:enabled="true" > + </service> + <service android:name=".s.Target01" android:exported="true" android:enabled="true" > + </service> + <service android:name=".s.Target02" android:exported="true" android:enabled="true" > + </service> + <service android:name=".s.Target03" android:exported="true" android:enabled="true" > + </service> + <service android:name=".s.Target04" android:exported="true" android:enabled="true" > + </service> + + <!-- + Due to http://go/intents-match-intent-filters-guide, the target intent has to have + an intent filter that matches the original intent. (modulo the package name) + This restriction shouldn't exist in the final version. + --> + <receiver android:name=".b.Target00" android:exported="true" android:enabled="true" > + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Target01" android:exported="true" android:enabled="true" > + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Target02" android:exported="true" android:enabled="true" > + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Target03" android:exported="true" android:enabled="true" > + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + <receiver android:name=".b.Target04" android:exported="true" android:enabled="true" > + <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter> + <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> + </receiver> + </application> +</manifest> diff --git a/tests/componentalias/AndroidManifest_sub1.xml b/tests/componentalias/AndroidManifest_sub1.xml new file mode 100755 index 000000000000..21616f5edf00 --- /dev/null +++ b/tests/componentalias/AndroidManifest_sub1.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.componentalias.tests" > + + <application> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.content.componentalias.tests.sub1" > + </instrumentation> +</manifest> diff --git a/tests/componentalias/AndroidManifest_sub2.xml b/tests/componentalias/AndroidManifest_sub2.xml new file mode 100755 index 000000000000..c11b0cd55ef4 --- /dev/null +++ b/tests/componentalias/AndroidManifest_sub2.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.componentalias.tests" > + + <application> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.content.componentalias.tests.sub2" > + </instrumentation> +</manifest> diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml new file mode 100644 index 000000000000..afdfe79ea4a4 --- /dev/null +++ b/tests/componentalias/AndroidTest-template.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="ComponentAliasTests.apk" /> + <option name="test-file-name" value="ComponentAliasTests1.apk" /> + <option name="test-file-name" value="ComponentAliasTests2.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. --> + <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" /> + <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" /> + <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub2" /> + + <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests" /> + <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub1" /> + <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub2" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="{PACKAGE}" /> + <option name="runtime-hint" value="2m" /> + <option name="isolated-storage" value="false" /> + </test> +</configuration> diff --git a/tests/componentalias/OWNERS b/tests/componentalias/OWNERS new file mode 100644 index 000000000000..1073c817d793 --- /dev/null +++ b/tests/componentalias/OWNERS @@ -0,0 +1,2 @@ +omakoto@google.com +yamasani@google.com
\ No newline at end of file diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java new file mode 100644 index 000000000000..99322ee46106 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests; + +import android.content.ComponentName; +import android.content.Context; +import android.os.Build; +import android.provider.DeviceConfig; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; + +import com.android.compatibility.common.util.DeviceConfigStateHelper; +import com.android.compatibility.common.util.ShellUtils; +import com.android.compatibility.common.util.TestUtils; + +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Before; + +import java.util.function.Consumer; + +public class BaseComponentAliasTest { + protected static final Context sContext = InstrumentationRegistry.getTargetContext(); + + protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); + @Before + public void enableComponentAliasWithCompatFlag() throws Exception { + Assume.assumeTrue(Build.isDebuggable()); + ShellUtils.runShellCommand( + "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); + sDeviceConfig.set("enable_experimental_component_alias", ""); + sDeviceConfig.set("component_alias_overrides", ""); + + // Make sure the feature is actually enabled, and the aliases are loaded. + TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { + String out = ShellUtils.runShellCommand("dumpsys activity component-alias"); + + return out.contains("Enabled: true") + && out.contains("android.content.componentalias.tests/.b.Alias04") + && out.contains("android.content.componentalias.tests/.s.Alias04"); + }); + ShellUtils.runShellCommand("am wait-for-broadcast-idle"); + } + + @AfterClass + public static void restoreDeviceConfig() throws Exception { + ShellUtils.runShellCommand( + "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); + sDeviceConfig.close(); + } + + protected static void log(String message) { + Log.i(ComponentAliasTestCommon.TAG, "[" + sContext.getPackageName() + "] " + message); + } + + /** + * Defines a test target. + */ + public static class Combo { + public final ComponentName alias; + public final ComponentName target; + public final String action; + + public Combo(ComponentName alias, ComponentName target, String action) { + this.alias = alias; + this.target = target; + this.action = action; + } + + @Override + public String toString() { + return "Combo{" + + "alias=" + toString(alias) + + ", target=" + toString(target) + + ", action='" + action + '\'' + + '}'; + } + + private static String toString(ComponentName cn) { + return cn == null ? "[null]" : cn.flattenToShortString(); + } + + public void apply(Consumer<Combo> callback) { + log("Testing for: " + this); + callback.accept(this); + } + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java new file mode 100644 index 000000000000..7d5e0b9c6d8a --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests; + +import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ComponentName; +import android.content.Intent; + +import com.android.compatibility.common.util.BroadcastMessenger.Receiver; + +import org.junit.Test; + +import java.util.function.Consumer; + +public class ComponentAliasBroadcastTest extends BaseComponentAliasTest { + private void forEachCombo(Consumer<Combo> callback) { + new Combo( + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias00"), + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Target00"), + MAIN_PACKAGE + ".IS_RECEIVER_00").apply(callback); + + new Combo( + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias01"), + new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".b.Target01"), + MAIN_PACKAGE + ".IS_RECEIVER_01").apply(callback); + new Combo( + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias02"), + new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".b.Target02"), + MAIN_PACKAGE + ".IS_RECEIVER_02").apply(callback); + } + + @Test + public void testBroadcast_explicitComponentName() { + forEachCombo((c) -> { + Intent i = new Intent().setComponent(c.alias); + i.setAction("ACTION_BROADCAST"); + ComponentAliasMessage m; + + try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { + log("Sending: " + i); + sContext.sendBroadcast(i); + + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onReceive"); + assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); + + // The broadcast intent will always have the receiving component name set. + assertThat(m.getIntent().getComponent()).isEqualTo(c.target); + + receiver.ensureNoMoreMessages(); + } + }); + } + + @Test + public void testBroadcast_explicitPackageName() { + forEachCombo((c) -> { + // In this test, we only set the package name to the intent. + // If the alias and target are the same package, the intent will be sent to both of them + // *and* the one to the alias is redirected to the target, so the target will receive + // the intent twice. This case is haled at *1 below. + + + Intent i = new Intent().setPackage(c.alias.getPackageName()); + i.setAction(c.action); + ComponentAliasMessage m; + + try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { + log("Sending broadcast: " + i); + sContext.sendBroadcast(i); + + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onReceive"); + assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); + assertThat(m.getIntent().getComponent()).isEqualTo(c.target); + + // *1 -- if the alias and target are in the same package, we expect one more + // message. + if (c.alias.getPackageName().equals(c.target.getPackageName())) { + m = receiver.waitForNextMessage(); + assertThat(m.getMethodName()).isEqualTo("onReceive"); + assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); + assertThat(m.getIntent().getComponent()).isEqualTo(c.target); + } + receiver.ensureNoMoreMessages(); + } + }); + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java new file mode 100644 index 000000000000..ee20379d971a --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java @@ -0,0 +1,63 @@ +/* + * 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.content.componentalias.tests; + +import android.os.Build; +import android.provider.DeviceConfig; + +import com.android.compatibility.common.util.DeviceConfigStateHelper; +import com.android.compatibility.common.util.ShellUtils; +import com.android.compatibility.common.util.TestUtils; + +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Test; + +public class ComponentAliasEnableWithDeviceConfigTest { + protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); + + @AfterClass + public static void restoreDeviceConfig() throws Exception { + sDeviceConfig.close(); + } + + @Test + public void enableComponentAliasWithCompatFlag() throws Exception { + Assume.assumeTrue(Build.isDebuggable()); + + sDeviceConfig.set("component_alias_overrides", ""); + + // First, disable with both compat-id and device config. + ShellUtils.runShellCommand( + "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); + sDeviceConfig.set("enable_experimental_component_alias", ""); + + TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { + return ShellUtils.runShellCommand("dumpsys activity component-alias") + .indexOf("Enabled: false") > 0; + }); + + // Then, enable by device config. + sDeviceConfig.set("enable_experimental_component_alias", "true"); + + // Make sure the feature is actually enabled. + TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { + return ShellUtils.runShellCommand("dumpsys activity component-alias") + .indexOf("Enabled: true") > 0; + }); + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java new file mode 100644 index 000000000000..d41696f27880 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * Parcelabe containing a "message" that's meant to be delivered via BroadcastMessenger. + * + * To add a new field, just add a private member field, and run: + * codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java + */ +@DataClass( + genConstructor = false, + genSetters = true, + genToString = true, + genAidl = false) +public final class ComponentAliasMessage implements Parcelable { + public ComponentAliasMessage() { + } + + @Nullable + private String mMessage; + + @Nullable + private String mMethodName; + + @Nullable + private String mSenderIdentity; + + @Nullable + private Intent mIntent; + + @Nullable + private ComponentName mComponent; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public @Nullable String getMessage() { + return mMessage; + } + + @DataClass.Generated.Member + public @Nullable String getMethodName() { + return mMethodName; + } + + @DataClass.Generated.Member + public @Nullable String getSenderIdentity() { + return mSenderIdentity; + } + + @DataClass.Generated.Member + public @Nullable Intent getIntent() { + return mIntent; + } + + @DataClass.Generated.Member + public @Nullable ComponentName getComponent() { + return mComponent; + } + + @DataClass.Generated.Member + public @NonNull ComponentAliasMessage setMessage(@NonNull String value) { + mMessage = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull ComponentAliasMessage setMethodName(@NonNull String value) { + mMethodName = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull ComponentAliasMessage setSenderIdentity(@NonNull String value) { + mSenderIdentity = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull ComponentAliasMessage setIntent(@NonNull Intent value) { + mIntent = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull ComponentAliasMessage setComponent(@NonNull ComponentName value) { + mComponent = value; + return this; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ComponentAliasMessage { " + + "message = " + mMessage + ", " + + "methodName = " + mMethodName + ", " + + "senderIdentity = " + mSenderIdentity + ", " + + "intent = " + mIntent + ", " + + "component = " + mComponent + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mMessage != null) flg |= 0x1; + if (mMethodName != null) flg |= 0x2; + if (mSenderIdentity != null) flg |= 0x4; + if (mIntent != null) flg |= 0x8; + if (mComponent != null) flg |= 0x10; + dest.writeByte(flg); + if (mMessage != null) dest.writeString(mMessage); + if (mMethodName != null) dest.writeString(mMethodName); + if (mSenderIdentity != null) dest.writeString(mSenderIdentity); + if (mIntent != null) dest.writeTypedObject(mIntent, flags); + if (mComponent != null) dest.writeTypedObject(mComponent, flags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ComponentAliasMessage(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String message = (flg & 0x1) == 0 ? null : in.readString(); + String methodName = (flg & 0x2) == 0 ? null : in.readString(); + String senderIdentity = (flg & 0x4) == 0 ? null : in.readString(); + Intent intent = (flg & 0x8) == 0 ? null : (Intent) in.readTypedObject(Intent.CREATOR); + ComponentName component = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR); + + this.mMessage = message; + this.mMethodName = methodName; + this.mSenderIdentity = senderIdentity; + this.mIntent = intent; + this.mComponent = component; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ComponentAliasMessage> CREATOR + = new Parcelable.Creator<ComponentAliasMessage>() { + @Override + public ComponentAliasMessage[] newArray(int size) { + return new ComponentAliasMessage[size]; + } + + @Override + public ComponentAliasMessage createFromParcel(@NonNull Parcel in) { + return new ComponentAliasMessage(in); + } + }; + + @DataClass.Generated( + time = 1630098801203L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java", + inputSignatures = "private @android.annotation.Nullable java.lang.String mMessage\nprivate @android.annotation.Nullable java.lang.String mMethodName\nprivate @android.annotation.Nullable java.lang.String mSenderIdentity\nprivate @android.annotation.Nullable android.content.Intent mIntent\nprivate @android.annotation.Nullable android.content.ComponentName mComponent\nclass ComponentAliasMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true, genToString=true, genAidl=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java new file mode 100644 index 000000000000..0899886fe951 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java @@ -0,0 +1,60 @@ +/* + * 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.content.componentalias.tests; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import android.provider.DeviceConfig; + +import com.android.compatibility.common.util.DeviceConfigStateHelper; +import com.android.compatibility.common.util.ShellUtils; + +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Test; + +/** + * Test to make sure component-alias can't be enabled on user builds. + */ +public class ComponentAliasNotSupportedOnUserBuildTest { + protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); + + @AfterClass + public static void restoreDeviceConfig() throws Exception { + sDeviceConfig.close(); + } + + @Test + public void enableComponentAliasWithCompatFlag() throws Exception { + Assume.assumeFalse(Build.isDebuggable()); + + // Try to enable it by both the device config and compat-id. + sDeviceConfig.set("enable_experimental_component_alias", "true"); + ShellUtils.runShellCommand( + "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); + + // Sleep for an arbitrary amount of time, so the config would sink in, if there was + // no "not on user builds" check. + + Thread.sleep(5000); + + // Make sure the feature is still disabled. + assertThat(ShellUtils.runShellCommand("dumpsys activity component-alias") + .indexOf("Enabled: false") > 0).isTrue(); + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java new file mode 100644 index 000000000000..f0ff088815af --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.componentalias.tests; + +import static android.content.Context.BIND_AUTO_CREATE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE; +import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; + +import static com.google.common.truth.Truth.assertThat; + +import static org.hamcrest.core.IsNot.not; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +import com.android.compatibility.common.util.BroadcastMessenger; +import com.android.compatibility.common.util.BroadcastMessenger.Receiver; +import com.android.compatibility.common.util.ShellUtils; +import com.android.compatibility.common.util.TestUtils; + +import org.junit.Assume; +import org.junit.Test; + +import java.util.function.Consumer; + +/** + * Test for the experimental "Component alias" feature. + * + * Note this test exercises the relevant APIs, but don't actually check if the aliases are + * resolved. + * + * Note all the helper APKs are battery-exempted (via AndroidTest.xml), so they can run + * BG services. + */ +public class ComponentAliasServiceTest extends BaseComponentAliasTest { + /** + * Service connection used throughout the tests. It sends a message for each callback via + * the messenger. + */ + private static final ServiceConnection sServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + log("onServiceConnected: " + name); + + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity("sServiceConnection") + .setMethodName("onServiceConnected") + .setComponent(name); + + BroadcastMessenger.send(sContext, TAG, m); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + log("onServiceDisconnected: " + name); + + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity("sServiceConnection") + .setMethodName("onServiceDisconnected") + .setComponent(name); + + BroadcastMessenger.send(sContext, TAG, m); + } + + @Override + public void onBindingDied(ComponentName name) { + log("onBindingDied: " + name); + + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity("sServiceConnection") + .setMethodName("onBindingDied"); + + BroadcastMessenger.send(sContext, TAG, m); + } + + @Override + public void onNullBinding(ComponentName name) { + log("onNullBinding: " + name); + + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity("sServiceConnection") + .setMethodName("onNullBinding"); + + BroadcastMessenger.send(sContext, TAG, m); + } + }; + + private void testStartAndStopService_common( + Intent originalIntent, + ComponentName componentNameForClient, + ComponentName componentNameForTarget) { + + ComponentAliasMessage m; + + try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { + // Start the service. + ComponentName result = sContext.startService(originalIntent); + assertThat(result).isEqualTo(componentNameForClient); + + // Check + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onStartCommand"); + // The app sees the rewritten intent. + assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget); + + // Verify the original intent. + assertThat(m.getIntent().getOriginalIntent().getComponent()) + .isEqualTo(originalIntent.getComponent()); + assertThat(m.getIntent().getOriginalIntent().getPackage()) + .isEqualTo(originalIntent.getPackage()); + + // Stop the service. + sContext.stopService(originalIntent); + + // Check + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onDestroy"); + + receiver.ensureNoMoreMessages(); + } + } + + private void forEachCombo(Consumer<Combo> callback) { + new Combo( + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias00"), + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target00"), + MAIN_PACKAGE + ".IS_ALIAS_00").apply(callback); + new Combo( + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"), + new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".s.Target01"), + MAIN_PACKAGE + ".IS_ALIAS_01").apply(callback); + new Combo( + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"), + new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"), + MAIN_PACKAGE + ".IS_ALIAS_02").apply(callback); + } + + @Test + public void testStartAndStopService_explicitComponentName() { + forEachCombo((c) -> { + Intent i = new Intent().setComponent(c.alias); + testStartAndStopService_common(i, c.alias, c.target); + }); + } + + @Test + public void testStartAndStopService_explicitPackageName() { + forEachCombo((c) -> { + Intent i = new Intent().setPackage(c.alias.getPackageName()); + i.setAction(c.action); + + testStartAndStopService_common(i, c.alias, c.target); + }); + } + + @Test + public void testStartAndStopService_override() throws Exception { + Intent i = new Intent().setPackage(MAIN_PACKAGE); + i.setAction(MAIN_PACKAGE + ".IS_ALIAS_01"); + + // Change some of the aliases from what's defined in <meta-data>. + + ComponentName aliasA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"); + ComponentName targetA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target02"); + + ComponentName aliasB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"); + ComponentName targetB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target01"); + + sDeviceConfig.set("component_alias_overrides", + aliasA.flattenToShortString() + ":" + targetA.flattenToShortString() + + "," + + aliasB.flattenToShortString() + ":" + targetB.flattenToShortString()); + + TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { + return ShellUtils.runShellCommand("dumpsys activity component-alias") + .indexOf(aliasA.flattenToShortString() + + " -> " + targetA.flattenToShortString()) > 0; + }); + + + testStartAndStopService_common(i, aliasA, targetA); + } + + private void testBindAndUnbindService_common( + Intent originalIntent, + ComponentName componentNameForClient, + ComponentName componentNameForTarget) { + ComponentAliasMessage m; + + try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { + // Bind to the service. + assertThat(sContext.bindService( + originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue(); + + // Check the target side behavior. + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onBind"); + // The app sees the rewritten intent. + assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget); + + // Verify the original intent. + assertThat(m.getIntent().getOriginalIntent().getComponent()) + .isEqualTo(originalIntent.getComponent()); + assertThat(m.getIntent().getOriginalIntent().getPackage()) + .isEqualTo(originalIntent.getPackage()); + + // Check the client side behavior. + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onServiceConnected"); + // The app sees the rewritten intent. + assertThat(m.getComponent()).isEqualTo(componentNameForClient); + + // Unbind. + sContext.unbindService(sServiceConnection); + + // Check the target side behavior. + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onDestroy"); + + // Note onServiceDisconnected() won't be called in this case. + receiver.ensureNoMoreMessages(); + } + } + + @Test + public void testBindService_explicitComponentName() { + forEachCombo((c) -> { + Intent i = new Intent().setComponent(c.alias); + + testBindAndUnbindService_common(i, c.alias, c.target); + }); + + } + + @Test + public void testBindService_explicitPackageName() { + forEachCombo((c) -> { + Intent i = new Intent().setPackage(c.alias.getPackageName()); + i.setAction(c.action); + + testBindAndUnbindService_common(i, c.alias, c.target); + }); + } + + /** + * Make sure, when the service process is killed, the client will get a callback with the + * right component name. + */ + @Test + public void testBindService_serviceKilled() { + + // We need to kill SUB2_PACKAGE, don't run it for this package. + Assume.assumeThat(sContext.getPackageName(), not(SUB2_PACKAGE)); + + Intent originalIntent = new Intent().setPackage(MAIN_PACKAGE); + originalIntent.setAction(MAIN_PACKAGE + ".IS_ALIAS_02"); + + final ComponentName componentNameForClient = + new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"); + final ComponentName componentNameForTarget = + new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"); + + ComponentAliasMessage m; + + try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { + // Bind to the service. + assertThat(sContext.bindService( + originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue(); + + // Check the target side behavior. + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onBind"); + + m = receiver.waitForNextMessage(); + assertThat(m.getMethodName()).isEqualTo("onServiceConnected"); + assertThat(m.getComponent()).isEqualTo(componentNameForClient); + // We don't need to check all the fields because these are tested else where. + + // Now kill the service process. + ShellUtils.runShellCommand("su 0 killall %s", SUB2_PACKAGE); + + // Check the target side behavior. + m = receiver.waitForNextMessage(); + + assertThat(m.getMethodName()).isEqualTo("onServiceDisconnected"); + assertThat(m.getComponent()).isEqualTo(componentNameForClient); + + receiver.ensureNoMoreMessages(); + } + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java new file mode 100644 index 000000000000..165d728c92a6 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests; + +public final class ComponentAliasTestCommon { + private ComponentAliasTestCommon() { + } + + public static final String TAG = "ComponentAliasTest"; + + public static final String MAIN_PACKAGE = "android.content.componentalias.tests"; + + public static final String SUB1_PACKAGE = "android.content.componentalias.tests.sub1"; + public static final String SUB2_PACKAGE = "android.content.componentalias.tests.sub2"; +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java new file mode 100644 index 000000000000..1d05e72a2f3f --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.b; + +import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.componentalias.tests.ComponentAliasMessage; +import android.util.Log; + +import com.android.compatibility.common.util.BroadcastMessenger; + +public class BaseReceiver extends BroadcastReceiver { + private String getMyIdentity(Context context) { + return (new ComponentName(context.getPackageName(), this.getClass().getCanonicalName())) + .flattenToShortString(); + } + + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "onReceive: on " + getMyIdentity(context) + " intent=" + intent); + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity(getMyIdentity(context)) + .setMethodName("onReceive") + .setIntent(intent); + BroadcastMessenger.send(context, TAG, m); + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java new file mode 100644 index 000000000000..8fb4e91f790c --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.b; + +import android.content.componentalias.tests.s.BaseService; + +public class Target00 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java new file mode 100644 index 000000000000..06f7a13f73d7 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.b; + +public class Target01 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java new file mode 100644 index 000000000000..df7579d8304d --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.b; + +public class Target02 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java new file mode 100644 index 000000000000..5ae55215f696 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.b; + +public class Target03 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java new file mode 100644 index 000000000000..f9b9886b0bb2 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.b; + +public class Target04 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java new file mode 100644 index 000000000000..535d9b80f100 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.s; + +import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.content.componentalias.tests.ComponentAliasMessage; +import android.os.Binder; +import android.os.IBinder; +import android.util.Log; + +import com.android.compatibility.common.util.BroadcastMessenger; + +public class BaseService extends Service { + private String getMyIdentity() { + return (new ComponentName(this.getPackageName(), this.getClass().getCanonicalName())) + .flattenToShortString(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i(TAG, "onStartCommand: on " + getMyIdentity() + " intent=" + intent); + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity(getMyIdentity()) + .setMethodName("onStartCommand") + .setIntent(intent); + BroadcastMessenger.send(this, TAG, m); + + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + Log.i(TAG, "onDestroy: on " + getMyIdentity()); + + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity(getMyIdentity()) + .setMethodName("onDestroy"); + BroadcastMessenger.send(this, TAG, m); + } + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "onBind: on " + getMyIdentity() + " intent=" + intent); + + ComponentAliasMessage m = new ComponentAliasMessage() + .setSenderIdentity(getMyIdentity()) + .setMethodName("onBind") + .setIntent(intent); + BroadcastMessenger.send(this, TAG, m); + + return new Binder(); + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java new file mode 100644 index 000000000000..64b91f5695f5 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.s; + +public class Target00 extends BaseService { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java new file mode 100644 index 000000000000..bd589991d7dc --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.s; + +public class Target01 extends BaseService { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java new file mode 100644 index 000000000000..0ddf8188768b --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.s; + +public class Target02 extends BaseService { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java new file mode 100644 index 000000000000..0dbc0501b6f9 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.s; + +public class Target03 extends BaseService { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java new file mode 100644 index 000000000000..099425867f02 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.componentalias.tests.s; + +public class Target04 extends BaseService { +} diff --git a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java b/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java index 7cda977d2115..5d639f6f6266 100644 --- a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java +++ b/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java @@ -409,10 +409,10 @@ public class NotificationTests extends AndroidTestCase { sleepIfYouCan(500); L("Parceling notifications..."); - // we want to be able to use this test on older OSes that do not have getBlobAshmemSize - Method getBlobAshmemSize = null; + // we want to be able to use this test on older OSes that do not have getOpenAshmemSize + Method getOpenAshmemSize = null; try { - getBlobAshmemSize = Parcel.class.getMethod("getBlobAshmemSize"); + getOpenAshmemSize = Parcel.class.getMethod("getOpenAshmemSize"); } catch (NoSuchMethodException ex) { } for (int i=0; i<mNotifications.size(); i++) { @@ -424,8 +424,8 @@ public class NotificationTests extends AndroidTestCase { time = SystemClock.currentThreadTimeMillis() - time; L(" %s: write parcel=%dms size=%d ashmem=%s", summarize(n), time, p.dataPosition(), - (getBlobAshmemSize != null) - ? getBlobAshmemSize.invoke(p) + (getOpenAshmemSize != null) + ? getOpenAshmemSize.invoke(p) : "???"); p.setDataPosition(0); } diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java index 84448333a8c6..c25ce71207ae 100644 --- a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java +++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java @@ -21,7 +21,6 @@ import static org.junit.Assert.assertTrue; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil; import org.junit.Assert; import org.junit.ClassRule; @@ -207,17 +206,6 @@ public class SystemPreparer extends ExternalResource { default: device.executeShellCommand("stop"); device.executeShellCommand("start"); - ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode(); - device.setRecoveryMode(ITestDevice.RecoveryMode.ONLINE); - - if (device.isEncryptionSupported()) { - if (device.isDeviceEncrypted()) { - LogUtil.CLog.e("Device is encrypted after userspace reboot!"); - device.unlockDevice(); - } - } - - device.setRecoveryMode(cachedRecoveryMode); device.waitForDeviceAvailable(); break; } @@ -380,7 +368,9 @@ public class SystemPreparer extends ExternalResource { device.executeShellCommand("disable-verity"); device.reboot(); } - device.executeShellCommand("remount"); + device.enableAdbRoot(); + device.remountSystemWritable(); + device.remountVendorWritable(); device.waitForDeviceAvailable(); } diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp index af9786b92f40..deff42a27f47 100644 --- a/tests/utils/testutils/Android.bp +++ b/tests/utils/testutils/Android.bp @@ -38,6 +38,6 @@ java_library { "android.test.runner", "android.test.base", "android.test.mock", - "mockito-target-minus-junit4", + "mockito-target-extended-minus-junit4", ], } diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 824f91e1e826..15a6afc5ff7c 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -62,6 +62,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.PendingInsetsControllerTest", "android.window.", // all tests under the package. "android.app.activity.ActivityThreadTest", + "android.app.activity.RegisterComponentCallbacksTest" }; public FrameworksTestsFilter(Bundle testArgs) { diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp index 41f73cd9c706..228520e8545b 100644 --- a/tests/vcn/Android.bp +++ b/tests/vcn/Android.bp @@ -18,6 +18,7 @@ android_test { "java/**/*.kt", ], platform_apis: true, + defaults: ["framework-connectivity-test-defaults"], test_suites: ["device-tests"], certificate: "platform", static_libs: [ @@ -28,6 +29,7 @@ android_test { "net-tests-utils", "platform-test-annotations", "services.core", + "service-connectivity-tiramisu-pre-jarjar", ], libs: [ "android.test.runner", diff --git a/tests/vcn/AndroidManifest.xml b/tests/vcn/AndroidManifest.xml index 2ad9aac67029..a8f657c89f76 100644 --- a/tests/vcn/AndroidManifest.xml +++ b/tests/vcn/AndroidManifest.xml @@ -16,7 +16,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.tests.vcn"> - + <uses-sdk android:minSdkVersion="33" + android:targetSdkVersion="33"/> <application> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/vcn/OWNERS b/tests/vcn/OWNERS index 33b9f0f75f81..2441e772468c 100644 --- a/tests/vcn/OWNERS +++ b/tests/vcn/OWNERS @@ -3,5 +3,5 @@ set noparent benedictwong@google.com ckesting@google.com evitayan@google.com +junyin@google.com nharold@google.com -jchalard@google.com
\ No newline at end of file diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java new file mode 100644 index 000000000000..2fbcf9d87bd4 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.vcn; + +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase { + private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>(); + private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>(); + + // Package private for use in VcnGatewayConnectionConfigTest + static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() { + return new VcnCellUnderlyingNetworkTemplate.Builder() + .setMetered(MATCH_FORBIDDEN) + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .setOperatorPlmnIds(ALLOWED_PLMN_IDS) + .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS) + .setRoaming(MATCH_FORBIDDEN) + .setOpportunistic(MATCH_REQUIRED) + .build(); + } + + @Test + public void testBuilderAndGetters() { + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); + assertEquals( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitDownstreamBandwidthKbps()); + assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds()); + assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds()); + assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming()); + assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic()); + } + + @Test + public void testBuilderAndGettersForDefaultValues() { + final VcnCellUnderlyingNetworkTemplate networkPriority = + new VcnCellUnderlyingNetworkTemplate.Builder().build(); + assertEquals(MATCH_ANY, networkPriority.getMetered()); + + // Explicitly expect 0, as documented in Javadoc on setter methods. + assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps()); + + assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds()); + assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds()); + assertEquals(MATCH_ANY, networkPriority.getRoaming()); + assertEquals(MATCH_ANY, networkPriority.getOpportunistic()); + } + + @Test + public void testBuilderRequiresStricterEntryCriteria() { + try { + new VcnCellUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } + + try { + new VcnCellUnderlyingNetworkTemplate.Builder() + .setMinDownstreamBandwidthKbps( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testPersistableBundle() { + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + assertEquals( + networkPriority, + VcnUnderlyingNetworkTemplate.fromPersistableBundle( + networkPriority.toPersistableBundle())); + } +} diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index dc338ae0fdc7..2aef9ae7ca32 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -17,6 +17,8 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; +import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES; +import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -30,6 +32,7 @@ import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.IkeTunnelConnectionParams; import android.net.vcn.persistablebundleutils.IkeSessionParamsUtilsTest; import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtilsTest; +import android.os.PersistableBundle; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -37,7 +40,9 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -50,9 +55,17 @@ public class VcnGatewayConnectionConfigTest { }; public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; + private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES = + new ArrayList(); + static { Arrays.sort(EXPOSED_CAPS); Arrays.sort(UNDERLYING_CAPS); + + UNDERLYING_NETWORK_TEMPLATES.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + UNDERLYING_NETWORK_TEMPLATES.add( + VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); } public static final long[] RETRY_INTERVALS_MS = @@ -82,7 +95,10 @@ public class VcnGatewayConnectionConfigTest { // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfig() { - return buildTestConfigWithExposedCaps(EXPOSED_CAPS); + final VcnGatewayConnectionConfig.Builder builder = + newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES); + + return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS); } private static VcnGatewayConnectionConfig.Builder newBuilder() { @@ -159,6 +175,15 @@ public class VcnGatewayConnectionConfigTest { } @Test + public void testBuilderRequiresNonNullNetworkTemplates() { + try { + newBuilder().setVcnUnderlyingNetworkPriorities(null); + fail("Expected exception due to invalid underlyingNetworkTemplates"); + } catch (NullPointerException e) { + } + } + + @Test public void testBuilderRequiresNonNullRetryInterval() { try { newBuilder().setRetryIntervalsMillis(null); @@ -195,6 +220,7 @@ public class VcnGatewayConnectionConfigTest { Arrays.sort(exposedCaps); assertArrayEquals(EXPOSED_CAPS, exposedCaps); + assertEquals(UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities()); assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams()); assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis()); @@ -208,6 +234,16 @@ public class VcnGatewayConnectionConfigTest { assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); } + @Test + public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() { + PersistableBundle configBundle = buildTestConfig().toPersistableBundle(); + configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null); + + final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle); + assertEquals( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities()); + } + private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) { final IkeSessionParams ikeParams = IkeSessionParamsUtilsTest.createBuilderMinimum() @@ -249,4 +285,37 @@ public class VcnGatewayConnectionConfigTest { assertNotEquals(tunnelParams, anotherTunnelParams); assertNotEquals(config, anotherConfig); } + + private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkTemplates( + List<VcnUnderlyingNetworkTemplate> networkTemplates) { + return buildTestConfigWithExposedCaps( + new VcnGatewayConnectionConfig.Builder( + "buildTestConfigWithVcnUnderlyingNetworkTemplates", + TUNNEL_CONNECTION_PARAMS) + .setVcnUnderlyingNetworkPriorities(networkTemplates), + EXPOSED_CAPS); + } + + @Test + public void testVcnUnderlyingNetworkTemplatesEquality() throws Exception { + final VcnGatewayConnectionConfig config = + buildTestConfigWithVcnUnderlyingNetworkTemplates(UNDERLYING_NETWORK_TEMPLATES); + + final List<VcnUnderlyingNetworkTemplate> networkTemplatesEqual = new ArrayList(); + networkTemplatesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + networkTemplatesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + final VcnGatewayConnectionConfig configEqual = + buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesEqual); + + final List<VcnUnderlyingNetworkTemplate> networkTemplatesNotEqual = new ArrayList(); + networkTemplatesNotEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + final VcnGatewayConnectionConfig configNotEqual = + buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesNotEqual); + + assertEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesEqual); + assertEquals(config, configEqual); + + assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual); + assertNotEquals(config, configNotEqual); + } } diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java new file mode 100644 index 000000000000..399e13600442 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.vcn; + +public class VcnUnderlyingNetworkTemplateTestBase { + // Public for use in NetworkPriorityClassifierTest + public static final int TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS = 200; + public static final int TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS = 100; + public static final int TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS = 400; + public static final int TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS = 300; +} diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java new file mode 100644 index 000000000000..4063178e005d --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.vcn; + +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import java.util.Set; + +public class VcnWifiUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase { + private static final String SSID = "TestWifi"; + + // Package private for use in VcnGatewayConnectionConfigTest + static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() { + return new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMetered(MATCH_FORBIDDEN) + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .setSsids(Set.of(SSID)) + .build(); + } + + @Test + public void testBuilderAndGetters() { + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); + assertEquals( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitDownstreamBandwidthKbps()); + assertEquals(Set.of(SSID), networkPriority.getSsids()); + } + + @Test + public void testBuilderAndGettersForDefaultValues() { + final VcnWifiUnderlyingNetworkTemplate networkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder().build(); + assertEquals(MATCH_ANY, networkPriority.getMetered()); + + // Explicitly expect 0, as documented in Javadoc on setter methods.. + assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps()); + + assertTrue(networkPriority.getSsids().isEmpty()); + } + + @Test + public void testBuilderRequiresStricterEntryCriteria() { + try { + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } + + try { + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinDownstreamBandwidthKbps( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testPersistableBundle() { + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + assertEquals( + networkPriority, + VcnUnderlyingNetworkTemplate.fromPersistableBundle( + networkPriority.toPersistableBundle())); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java index f3851130c68a..3b201f9d20dd 100644 --- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java @@ -135,11 +135,12 @@ public class IkeSessionParamsUtilsTest { } @Test - public void testEncodeRecodeParamsWithIkeOptions() throws Exception { + public void testEncodeDecodeParamsWithIkeOptions() throws Exception { final IkeSessionParams params = createBuilderMinimum() .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID) .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) + .addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT) .build(); verifyPersistableBundleEncodeDecodeIsLossless(params); } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 7c7dc4d79e9a..f924b2e9b932 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -66,6 +66,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.Uri; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; @@ -115,18 +116,24 @@ import java.util.UUID; public class VcnManagementServiceTest { private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); + private static final String TEST_PACKAGE_NAME_2 = "TEST_PKG_2"; private static final String TEST_CB_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName() + ".callback"; private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1)); + private static final ParcelUuid TEST_UUID_3 = new ParcelUuid(new UUID(2, 2)); private static final VcnConfig TEST_VCN_CONFIG; + private static final VcnConfig TEST_VCN_CONFIG_PKG_2; private static final int TEST_UID = Process.FIRST_APPLICATION_UID; static { final Context mockConfigContext = mock(Context.class); - doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName(); + doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName(); TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext); + + doReturn(TEST_PACKAGE_NAME_2).when(mockConfigContext).getOpPackageName(); + TEST_VCN_CONFIG_PKG_2 = VcnConfigTest.buildTestConfig(mockConfigContext); } private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP = @@ -247,18 +254,24 @@ public class VcnManagementServiceTest { eq(android.Manifest.permission.NETWORK_FACTORY), any()); } + private void setupMockedCarrierPrivilege(boolean isPrivileged) { + setupMockedCarrierPrivilege(isPrivileged, TEST_PACKAGE_NAME); + } + + private void setupMockedCarrierPrivilege(boolean isPrivileged, String pkg) { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) .getSubscriptionsInGroup(any()); doReturn(mTelMgr) .when(mTelMgr) .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId())); - doReturn(isPrivileged - ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS - : CARRIER_PRIVILEGE_STATUS_NO_ACCESS) + doReturn( + isPrivileged + ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS + : CARRIER_PRIVILEGE_STATUS_NO_ACCESS) .when(mTelMgr) - .checkCarrierPrivilegesForPackage(eq(TEST_PACKAGE_NAME)); + .checkCarrierPrivilegesForPackage(eq(pkg)); } @Test @@ -414,7 +427,13 @@ public class VcnManagementServiceTest { private BroadcastReceiver getPackageChangeReceiver() { final ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mMockContext).registerReceiver(captor.capture(), any(), any(), any()); + verify(mMockContext).registerReceiver(captor.capture(), argThat(filter -> { + return filter.hasAction(Intent.ACTION_PACKAGE_ADDED) + && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED) + && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED) + && filter.hasAction(Intent.ACTION_PACKAGE_DATA_CLEARED) + && filter.hasAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + }), any(), any()); return captor.getValue(); } @@ -627,6 +646,44 @@ public class VcnManagementServiceTest { } @Test + public void testPackageChangeListener_packageDataCleared() throws Exception { + triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1)); + final Vcn vcn = mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1); + + final BroadcastReceiver receiver = getPackageChangeReceiver(); + assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs()); + + final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED); + intent.setData(Uri.parse("package:" + TEST_PACKAGE_NAME)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(TEST_UID)); + + receiver.onReceive(mMockContext, intent); + mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); + assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); + verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); + } + + @Test + public void testPackageChangeListener_packageFullyRemoved() throws Exception { + triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1)); + final Vcn vcn = mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1); + + final BroadcastReceiver receiver = getPackageChangeReceiver(); + assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs()); + + final Intent intent = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED); + intent.setData(Uri.parse("package:" + TEST_PACKAGE_NAME)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(TEST_UID)); + + receiver.onReceive(mMockContext, intent); + mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); + assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); + verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); + } + + @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); @@ -666,7 +723,7 @@ public class VcnManagementServiceTest { @Test public void testSetVcnConfigMismatchedPackages() throws Exception { try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage"); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME_2); fail("Expected exception due to mismatched packages in config and method call"); } catch (IllegalArgumentException expected) { verify(mMockPolicyListener, never()).onPolicyChanged(); @@ -766,11 +823,12 @@ public class VcnManagementServiceTest { } @Test - public void testClearVcnConfigRequiresCarrierPrivileges() throws Exception { + public void testClearVcnConfigRequiresCarrierPrivilegesOrProvisioningPackage() + throws Exception { setupMockedCarrierPrivilege(false); try { - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } @@ -779,20 +837,32 @@ public class VcnManagementServiceTest { @Test public void testClearVcnConfigMismatchedPackages() throws Exception { try { - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage"); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2); fail("Expected security exception due to mismatched packages"); } catch (SecurityException expected) { } } @Test - public void testClearVcnConfig() throws Exception { + public void testClearVcnConfig_callerIsProvisioningPackage() throws Exception { + // Lose carrier privileges to test that provisioning package is sufficient. + setupMockedCarrierPrivilege(false); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @Test + public void testClearVcnConfig_callerIsCarrierPrivileged() throws Exception { + setupMockedCarrierPrivilege(true, TEST_PACKAGE_NAME_2); + + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2); + assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); + verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); + } + + @Test public void testClearVcnConfigNotifiesStatusCallback() throws Exception { setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME); @@ -843,11 +913,12 @@ public class VcnManagementServiceTest { @Test public void testGetConfiguredSubscriptionGroupsMismatchedPackages() throws Exception { - final String badPackage = "IncorrectPackage"; - doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, badPackage); + doThrow(new SecurityException()) + .when(mAppOpsMgr) + .checkPackage(TEST_UID, TEST_PACKAGE_NAME_2); try { - mVcnMgmtSvc.getConfiguredSubscriptionGroups(badPackage); + mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME_2); fail("Expected security exception due to mismatched packages"); } catch (SecurityException expected) { } @@ -855,14 +926,16 @@ public class VcnManagementServiceTest { @Test public void testGetConfiguredSubscriptionGroups() throws Exception { + setupMockedCarrierPrivilege(true, TEST_PACKAGE_NAME_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_3, TEST_VCN_CONFIG_PKG_2, TEST_PACKAGE_NAME_2); - // Assert that if both UUID 1 and 2 are provisioned, the caller only gets ones that they are - // privileged for. + // Assert that if UUIDs 1, 2 and 3 are provisioned, the caller only gets ones that they are + // privileged for, or are the provisioning package of. triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1)); final List<ParcelUuid> subGrps = mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME); - assertEquals(Collections.singletonList(TEST_UUID_1), subGrps); + assertEquals(Arrays.asList(new ParcelUuid[] {TEST_UUID_1, TEST_UUID_2}), subGrps); } @Test diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 1f0df62fe72c..09080be9ee41 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -22,9 +22,11 @@ import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -34,8 +36,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -47,9 +51,11 @@ import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.vcn.VcnManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; +import android.os.PersistableBundle; import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; @@ -57,6 +63,8 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.CarrierPrivilegesCallback; +import android.util.ArrayMap; import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -83,7 +91,7 @@ public class TelephonySubscriptionTrackerTest { private static final String PACKAGE_NAME = TelephonySubscriptionTrackerTest.class.getPackage().getName(); private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); - private static final int TEST_SIM_SLOT_INDEX = 1; + private static final int TEST_SIM_SLOT_INDEX = 0; private static final int TEST_SUBSCRIPTION_ID_1 = 2; private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class); private static final int TEST_SUBSCRIPTION_ID_2 = 3; @@ -99,6 +107,26 @@ public class TelephonySubscriptionTrackerTest { TEST_SUBID_TO_INFO_MAP = Collections.unmodifiableMap(subIdToGroupMap); } + private static final String TEST_CARRIER_CONFIG_KEY_1 = "TEST_CARRIER_CONFIG_KEY_1"; + private static final String TEST_CARRIER_CONFIG_KEY_2 = "TEST_CARRIER_CONFIG_KEY_2"; + private static final PersistableBundle TEST_CARRIER_CONFIG = new PersistableBundle(); + private static final PersistableBundleWrapper TEST_CARRIER_CONFIG_WRAPPER; + private static final Map<Integer, PersistableBundleWrapper> TEST_SUBID_TO_CARRIER_CONFIG_MAP; + + static { + TEST_CARRIER_CONFIG.putString( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY); + TEST_CARRIER_CONFIG.putString( + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY); + TEST_CARRIER_CONFIG_WRAPPER = new PersistableBundleWrapper(TEST_CARRIER_CONFIG); + + final Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap = new HashMap<>(); + subIdToCarrierConfigMap.put(TEST_SUBSCRIPTION_ID_1, TEST_CARRIER_CONFIG_WRAPPER); + TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap); + } + @NonNull private final Context mContext; @NonNull private final TestLooper mTestLooper; @NonNull private final Handler mHandler; @@ -139,6 +167,9 @@ public class TelephonySubscriptionTrackerTest { doReturn(mCarrierConfigManager) .when(mContext) .getSystemService(Context.CARRIER_CONFIG_SERVICE); + doReturn(TEST_CARRIER_CONFIG) + .when(mCarrierConfigManager) + .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1)); // subId 1, 2 are in same subGrp, only subId 1 is active doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid(); @@ -151,6 +182,8 @@ public class TelephonySubscriptionTrackerTest { @Before public void setUp() throws Exception { + doReturn(2).when(mTelephonyManager).getActiveModemCount(); + mCallback = mock(TelephonySubscriptionTrackerCallback.class); mTelephonySubscriptionTracker = new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps); @@ -180,6 +213,15 @@ public class TelephonySubscriptionTrackerTest { return captor.getValue(); } + private List<CarrierPrivilegesCallback> getCarrierPrivilegesCallbacks() { + final ArgumentCaptor<CarrierPrivilegesCallback> captor = + ArgumentCaptor.forClass(CarrierPrivilegesCallback.class); + verify(mTelephonyManager, atLeastOnce()) + .registerCarrierPrivilegesCallback(anyInt(), any(), captor.capture()); + + return captor.getAllValues(); + } + private ActiveDataSubscriptionIdListener getActiveDataSubscriptionIdListener() { final ArgumentCaptor<TelephonyCallback> captor = ArgumentCaptor.forClass(TelephonyCallback.class); @@ -188,6 +230,11 @@ public class TelephonySubscriptionTrackerTest { return (ActiveDataSubscriptionIdListener) captor.getValue(); } + private Intent buildTestMultiSimConfigBroadcastIntent() { + Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED); + return intent; + } + private Intent buildTestBroadcastIntent(boolean hasValidSubscription) { Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED); intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX); @@ -206,14 +253,24 @@ public class TelephonySubscriptionTrackerTest { private TelephonySubscriptionSnapshot buildExpectedSnapshot( Map<Integer, SubscriptionInfo> subIdToInfoMap, Map<ParcelUuid, Set<String>> privilegedPackages) { - return new TelephonySubscriptionSnapshot(0, subIdToInfoMap, privilegedPackages); + return buildExpectedSnapshot(0, subIdToInfoMap, privilegedPackages); } private TelephonySubscriptionSnapshot buildExpectedSnapshot( int activeSubId, Map<Integer, SubscriptionInfo> subIdToInfoMap, Map<ParcelUuid, Set<String>> privilegedPackages) { - return new TelephonySubscriptionSnapshot(activeSubId, subIdToInfoMap, privilegedPackages); + return buildExpectedSnapshot( + activeSubId, subIdToInfoMap, TEST_SUBID_TO_CARRIER_CONFIG_MAP, privilegedPackages); + } + + private TelephonySubscriptionSnapshot buildExpectedSnapshot( + int activeSubId, + Map<Integer, SubscriptionInfo> subIdToInfoMap, + Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap, + Map<ParcelUuid, Set<String>> privilegedPackages) { + return new TelephonySubscriptionSnapshot( + activeSubId, subIdToInfoMap, subIdToCarrierConfigMap, privilegedPackages); } private void verifyNoActiveSubscriptions() { @@ -224,6 +281,8 @@ public class TelephonySubscriptionTrackerTest { private void setupReadySubIds() { mTelephonySubscriptionTracker.setReadySubIdsBySlotId( Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1)); + mTelephonySubscriptionTracker.setSubIdToCarrierConfigMap( + Collections.singletonMap(TEST_SUBSCRIPTION_ID_1, TEST_CARRIER_CONFIG_WRAPPER)); } private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) { @@ -239,12 +298,21 @@ public class TelephonySubscriptionTrackerTest { any(), eq(mHandler)); final IntentFilter filter = getIntentFilter(); - assertEquals(1, filter.countActions()); + assertEquals(2, filter.countActions()); assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED)); + assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED)); verify(mSubscriptionManager) .addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any()); assertNotNull(getOnSubscriptionsChangedListener()); + + verify(mTelephonyManager, times(2)) + .registerCarrierPrivilegesCallback(anyInt(), any(HandlerExecutor.class), any()); + verify(mTelephonyManager) + .registerCarrierPrivilegesCallback(eq(0), any(HandlerExecutor.class), any()); + verify(mTelephonyManager) + .registerCarrierPrivilegesCallback(eq(1), any(HandlerExecutor.class), any()); + assertEquals(2, getCarrierPrivilegesCallbacks().size()); } @Test @@ -255,6 +323,50 @@ public class TelephonySubscriptionTrackerTest { final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(eq(listener)); + + for (CarrierPrivilegesCallback carrierPrivilegesCallback : + getCarrierPrivilegesCallbacks()) { + verify(mTelephonyManager) + .unregisterCarrierPrivilegesCallback(eq(carrierPrivilegesCallback)); + } + } + + @Test + public void testMultiSimConfigChanged() throws Exception { + final ArrayMap<Integer, Integer> readySubIdsBySlotId = new ArrayMap<>(); + readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1); + readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX + 1, TEST_SUBSCRIPTION_ID_1); + + mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId); + mTelephonySubscriptionTracker.setSubIdToCarrierConfigMap(TEST_SUBID_TO_CARRIER_CONFIG_MAP); + doReturn(1).when(mTelephonyManager).getActiveModemCount(); + + List<CarrierPrivilegesCallback> carrierPrivilegesCallbacks = + getCarrierPrivilegesCallbacks(); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestMultiSimConfigBroadcastIntent()); + mTestLooper.dispatchAll(); + + for (CarrierPrivilegesCallback carrierPrivilegesCallback : carrierPrivilegesCallbacks) { + verify(mTelephonyManager) + .unregisterCarrierPrivilegesCallback(eq(carrierPrivilegesCallback)); + } + + // Expect cache cleared for inactive slots. + assertNull( + mTelephonySubscriptionTracker + .getReadySubIdsBySlotId() + .get(TEST_SIM_SLOT_INDEX + 1)); + + // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other + // (2 previously registered during startup, for slots 0 & 1) + verify(mTelephonyManager, times(3)) + .registerCarrierPrivilegesCallback(anyInt(), any(HandlerExecutor.class), any()); + verify(mTelephonyManager, times(2)) + .registerCarrierPrivilegesCallback(eq(0), any(HandlerExecutor.class), any()); + + // Verify that this triggers a re-evaluation + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -314,6 +426,17 @@ public class TelephonySubscriptionTrackerTest { } @Test + public void testOnCarrierPrivilegesChanged() throws Exception { + setupReadySubIds(); + + final CarrierPrivilegesCallback callback = getCarrierPrivilegesCallbacks().get(0); + callback.onCarrierPrivilegesChanged(Collections.emptySet(), Collections.emptySet()); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); + } + + @Test public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); @@ -380,8 +503,16 @@ public class TelephonySubscriptionTrackerTest { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap()))); + verify(mCallback) + .onNewSnapshot( + eq( + buildExpectedSnapshot( + 0, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap()))); assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); + assertNull( + mTelephonySubscriptionTracker + .getSubIdToCarrierConfigMap() + .get(TEST_SUBSCRIPTION_ID_1)); } @Test @@ -409,7 +540,7 @@ public class TelephonySubscriptionTrackerTest { public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception { final TelephonySubscriptionSnapshot snapshot = new TelephonySubscriptionSnapshot( - TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap()); + TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap()); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1)); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2)); @@ -419,7 +550,7 @@ public class TelephonySubscriptionTrackerTest { public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception { final TelephonySubscriptionSnapshot snapshot = new TelephonySubscriptionSnapshot( - TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap()); + TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap()); assertEquals( new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index a687bb893c4a..15d4f1097108 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -56,6 +56,7 @@ import android.net.LinkProperties; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeSessionConnectionInfo; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.ipsec.ike.exceptions.IkeProtocolException; @@ -121,7 +122,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection doReturn(false).when(mDeps).isAirplaneModeOn(any()); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -135,7 +136,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection doReturn(true).when(mDeps).isAirplaneModeOn(any()); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -146,7 +147,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -158,7 +159,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testSameNetworkDoesNotTriggerMigration() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -216,14 +217,23 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testMigration() throws Exception { triggerChildOpened(); + mTestLooper.dispatchAll(); + assertEquals(mIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo()); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); + + final IkeSessionConnectionInfo newIkeConnectionInfo = + new IkeSessionConnectionInfo( + TEST_ADDR_V4, TEST_ADDR_V4_2, TEST_UNDERLYING_NETWORK_RECORD_2.network); + getIkeSessionCallback().onIkeSessionConnectionInfoChanged(newIkeConnectionInfo); getChildSessionCallback() .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); mTestLooper.dispatchAll(); + assertEquals(newIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo()); + verify(mIpSecSvc, times(2)) .setNetworkForTunnelInterface( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), @@ -246,12 +256,16 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection MtuUtils.getMtu( saProposals, mConfig.getMaxMtu(), - TEST_UNDERLYING_NETWORK_RECORD_2.linkProperties.getMtu()); + TEST_UNDERLYING_NETWORK_RECORD_2.linkProperties.getMtu(), + true /* isIpv4 */); verify(mNetworkAgent).sendLinkProperties( argThat(lp -> expectedMtu == lp.getMtu() && TEST_TCP_BUFFER_SIZES_2.equals(lp.getTcpBufferSizes()))); verify(mNetworkAgent) .setUnderlyingNetworks(eq(singletonList(TEST_UNDERLYING_NETWORK_RECORD_2.network))); + + // Verify revalidation is triggered on VCN network + verify(mConnMgr).reportNetworkConnectivity(eq(mNetworkAgent.getNetwork()), eq(false)); } private void triggerChildOpened() { @@ -266,6 +280,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection .when(mMockChildSessionConfig) .getInternalDnsServers(); + getIkeSessionCallback().onOpened(mIkeSessionConfiguration); getChildSessionCallback().onOpened(mMockChildSessionConfig); } @@ -295,6 +310,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + assertEquals(mIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo()); final ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); @@ -315,8 +331,6 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection any(), any()); verify(mNetworkAgent).register(); - verify(mNetworkAgent) - .setUnderlyingNetworks(eq(singletonList(TEST_UNDERLYING_NETWORK_RECORD_1.network))); verify(mNetworkAgent).markConnected(); verify(mIpSecSvc) @@ -331,6 +345,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection final NetworkCapabilities nc = ncCaptor.getValue(); assertTrue(nc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertEquals(List.of(TEST_UNDERLYING_NETWORK_RECORD_1.network), nc.getUnderlyingNetworks()); for (int cap : mConfig.getAllExposedCapabilities()) { assertTrue(nc.hasCapability(cap)); } @@ -426,6 +441,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerValidation(NetworkAgent.VALIDATION_STATUS_NOT_VALID); mTestLooper.dispatchAll(); + verify(mConnMgr) + .reportNetworkConnectivity(eq(TEST_UNDERLYING_NETWORK_RECORD_1.network), eq(false)); + final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mDeps, times(2)) .newWakeupMessage( diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index d1f3a210d870..3c70759a2fa6 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -64,7 +64,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testNullNetworkTriggersDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -76,7 +76,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testNewNetworkTriggersReconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -89,7 +89,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testSameNetworkDoesNotTriggerReconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 2056eea42ce6..f3eb82f46de7 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -78,7 +78,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testNetworkChangesTriggerStateTransitions() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -89,7 +89,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testNullNetworkDoesNotTriggerStateTransition() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 1c859790a2fe..6568cdd44377 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -58,7 +58,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testNewNetworkTriggerRetry() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -72,7 +72,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testSameNetworkDoesNotTriggerRetry() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -86,7 +86,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testNullNetworkTriggersDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index b8eefabea3c6..6a9a1e22cab1 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -59,7 +59,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import org.junit.Before; import org.junit.Test; @@ -133,8 +133,9 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { capBuilder.setLinkUpstreamBandwidthKbps(TEST_UPSTREAM_BANDWIDTH); capBuilder.setLinkDownstreamBandwidthKbps(TEST_DOWNSTREAM_BANDWIDTH); capBuilder.setAdministratorUids(new int[] {TEST_UID}); + final Network underlyingNetwork = mock(Network.class, CALLS_REAL_METHODS); UnderlyingNetworkRecord record = new UnderlyingNetworkRecord( - mock(Network.class, CALLS_REAL_METHODS), + underlyingNetwork, capBuilder.build(), new LinkProperties(), false); final NetworkCapabilities vcnCaps = VcnGatewayConnection.buildNetworkCapabilities( @@ -145,6 +146,7 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(vcnCaps.getUnderlyingNetworks().equals(List.of(underlyingNetwork))); for (int cap : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { if (cap == NET_CAPABILITY_INTERNET || cap == NET_CAPABILITY_DUN) { @@ -211,7 +213,8 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { VcnGatewayConnectionConfigTest.buildTestConfig(), tunnelIface, childSessionConfig, - record); + record, + mIkeConnectionInfo); verify(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE); @@ -224,7 +227,8 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { VcnGatewayConnectionConfigTest.buildTestConfig(), tunnelIface, childSessionConfig, - record); + record, + mIkeConnectionInfo); verify(mDeps, times(2)).getUnderlyingIfaceMtu(LOOPBACK_IFACE); @@ -236,14 +240,14 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { } @Test - public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { + public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkController() { verifyWakeLockSetUp(); final TelephonySubscriptionSnapshot updatedSnapshot = mock(TelephonySubscriptionSnapshot.class); mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot); - verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot)); + verify(mUnderlyingNetworkController).updateSubscriptionSnapshot(eq(updatedSnapshot)); verifyWakeLockAcquired(); mTestLooper.dispatchAll(); @@ -254,13 +258,13 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { @Test public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); verify(mDisconnectRequestAlarm).cancel(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 64d0bca15ce9..785bff167ad2 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -16,7 +16,6 @@ package com.android.server.vcn; -import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; @@ -48,6 +47,8 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.IkeSessionCallback; +import android.net.ipsec.ike.IkeSessionConfiguration; +import android.net.ipsec.ike.IkeSessionConnectionInfo; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; @@ -62,6 +63,8 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback; import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock; +import com.android.server.vcn.routeselection.UnderlyingNetworkController; +import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -79,6 +82,13 @@ public class VcnGatewayConnectionTestBase { doReturn(TEST_SUB_GRP).when(TEST_SUB_INFO).getGroupUuid(); } + protected static final InetAddress TEST_ADDR = InetAddresses.parseNumericAddress("2001:db8::1"); + protected static final InetAddress TEST_ADDR_2 = + InetAddresses.parseNumericAddress("2001:db8::2"); + protected static final InetAddress TEST_ADDR_V4 = + InetAddresses.parseNumericAddress("192.0.2.1"); + protected static final InetAddress TEST_ADDR_V4_2 = + InetAddresses.parseNumericAddress("192.0.2.2"); protected static final InetAddress TEST_DNS_ADDR = InetAddresses.parseNumericAddress("2001:DB8:0:1::"); protected static final InetAddress TEST_DNS_ADDR_2 = @@ -128,6 +138,7 @@ public class VcnGatewayConnectionTestBase { new TelephonySubscriptionSnapshot( TEST_SUB_ID, Collections.singletonMap(TEST_SUB_ID, TEST_SUB_INFO), + Collections.EMPTY_MAP, Collections.EMPTY_MAP); @NonNull protected final Context mContext; @@ -137,7 +148,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final VcnGatewayConnectionConfig mConfig; @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull protected final VcnGatewayConnection.Dependencies mDeps; - @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + @NonNull protected final UnderlyingNetworkController mUnderlyingNetworkController; @NonNull protected final VcnWakeLock mWakeLock; @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; @NonNull protected final WakeupMessage mDisconnectRequestAlarm; @@ -147,6 +158,9 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final IpSecService mIpSecSvc; @NonNull protected final ConnectivityManager mConnMgr; + @NonNull protected final IkeSessionConnectionInfo mIkeConnectionInfo; + @NonNull protected final IkeSessionConfiguration mIkeSessionConfiguration; + protected VcnIkeSession mMockIkeSession; protected VcnGatewayConnection mGatewayConnection; @@ -158,7 +172,7 @@ public class VcnGatewayConnectionTestBase { mConfig = VcnGatewayConnectionConfigTest.buildTestConfig(); mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); - mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); + mUnderlyingNetworkController = mock(UnderlyingNetworkController.class); mWakeLock = mock(VcnWakeLock.class); mTeardownTimeoutAlarm = mock(WakeupMessage.class); mDisconnectRequestAlarm = mock(WakeupMessage.class); @@ -172,13 +186,17 @@ public class VcnGatewayConnectionTestBase { VcnTestUtils.setupSystemService( mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + mIkeConnectionInfo = + new IkeSessionConnectionInfo(TEST_ADDR, TEST_ADDR_2, mock(Network.class)); + mIkeSessionConfiguration = new IkeSessionConfiguration.Builder(mIkeConnectionInfo).build(); + doReturn(mContext).when(mVcnContext).getContext(); doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); - doReturn(mUnderlyingNetworkTracker) + doReturn(mUnderlyingNetworkController) .when(mDeps) - .newUnderlyingNetworkTracker(any(), any(), any(), any()); + .newUnderlyingNetworkController(any(), any(), any(), any(), any()); doReturn(mWakeLock) .when(mDeps) .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java new file mode 100644 index 000000000000..b0d68952c39d --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -0,0 +1,526 @@ +/* + * 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.server.vcn.routeselection; + +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS; + +import static com.android.server.vcn.VcnTestUtils.setupSystemService; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.calculatePriorityClass; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule; +import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnCellUnderlyingNetworkTemplate; +import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnManager; +import android.net.vcn.VcnWifiUnderlyingNetworkTemplate; +import android.os.ParcelUuid; +import android.os.PersistableBundle; +import android.os.test.TestLooper; +import android.telephony.TelephonyManager; +import android.util.ArraySet; + +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Set; +import java.util.UUID; + +public class NetworkPriorityClassifierTest { + private static final String SSID = "TestWifi"; + private static final String SSID_OTHER = "TestWifiOther"; + private static final String PLMN_ID = "123456"; + private static final String PLMN_ID_OTHER = "234567"; + + private static final int SUB_ID = 1; + private static final int WIFI_RSSI = -60; + private static final int WIFI_RSSI_HIGH = -50; + private static final int WIFI_RSSI_LOW = -80; + private static final int CARRIER_ID = 1; + private static final int CARRIER_ID_OTHER = 2; + + private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024; + private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048; + + private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100; + private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200; + + private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); + + private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSignalStrength(WIFI_RSSI) + .setSsid(SSID) + .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS) + .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS) + .build(); + + private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER = + new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build(); + private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setSubscriptionIds(Set.of(SUB_ID)) + .setNetworkSpecifier(TEL_NETWORK_SPECIFIER) + .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS) + .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS) + .build(); + + private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface"); + + @Mock private Network mNetwork; + @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; + @Mock private TelephonyManager mTelephonyManager; + + private TestLooper mTestLooper; + private VcnContext mVcnContext; + private UnderlyingNetworkRecord mWifiNetworkRecord; + private UnderlyingNetworkRecord mCellNetworkRecord; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final Context mockContext = mock(Context.class); + mTestLooper = new TestLooper(); + mVcnContext = + spy( + new VcnContext( + mockContext, + mTestLooper.getLooper(), + mock(VcnNetworkProvider.class), + false /* isInTestMode */)); + doNothing().when(mVcnContext).ensureRunningOnLooperThread(); + + mWifiNetworkRecord = + new UnderlyingNetworkRecord( + mNetwork, + WIFI_NETWORK_CAPABILITIES, + LINK_PROPERTIES, + false /* isBlocked */); + + mCellNetworkRecord = + new UnderlyingNetworkRecord( + mNetwork, + CELL_NETWORK_CAPABILITIES, + LINK_PROPERTIES, + false /* isBlocked */); + + setupSystemService( + mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); + when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager); + when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID); + when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID); + } + + @Test + public void testMatchWithoutNotMeteredBit() { + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMetered(MATCH_FORBIDDEN) + .build(); + + assertFalse( + checkMatchesPriorityRule( + mVcnContext, + wifiNetworkPriority, + mWifiNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot, + null /* currentlySelecetd */, + null /* carrierConfig */)); + } + + private void verifyMatchesPriorityRuleForUpstreamBandwidth( + int entryUpstreamBandwidth, + int exitUpstreamBandwidth, + UnderlyingNetworkRecord currentlySelected, + boolean expectMatch) { + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps(entryUpstreamBandwidth, exitUpstreamBandwidth) + .build(); + + assertEquals( + expectMatch, + checkMatchesPriorityRule( + mVcnContext, + wifiNetworkPriority, + mWifiNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot, + currentlySelected, + null /* carrierConfig */)); + } + + private void verifyMatchesPriorityRuleForDownstreamBandwidth( + int entryDownstreamBandwidth, + int exitDownstreamBandwidth, + UnderlyingNetworkRecord currentlySelected, + boolean expectMatch) { + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinDownstreamBandwidthKbps( + entryDownstreamBandwidth, exitDownstreamBandwidth) + .build(); + + assertEquals( + expectMatch, + checkMatchesPriorityRule( + mVcnContext, + wifiNetworkPriority, + mWifiNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot, + currentlySelected, + null /* carrierConfig */)); + } + + @Test + public void testMatchWithEntryUpstreamBandwidthEquals() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + null /* currentlySelected */, + true); + } + + @Test + public void testMatchWithEntryUpstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + null /* currentlySelected */, + false); + } + + @Test + public void testMatchWithEntryDownstreamBandwidthEquals() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + null /* currentlySelected */, + true); + } + + @Test + public void testMatchWithEntryDownstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + null /* currentlySelected */, + false); + } + + @Test + public void testMatchWithExitUpstreamBandwidthEquals() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + mWifiNetworkRecord, + true); + } + + @Test + public void testMatchWithExitUpstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + mWifiNetworkRecord, + false); + } + + @Test + public void testMatchWithExitDownstreamBandwidthEquals() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + mWifiNetworkRecord, + true); + } + + @Test + public void testMatchWithExitDownstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + mWifiNetworkRecord, + false); + } + + private void verifyMatchWifi( + boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) { + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .build(); + final UnderlyingNetworkRecord selectedNetworkRecord = + isSelectedNetwork ? mWifiNetworkRecord : null; + assertEquals( + expectMatch, + checkMatchesWifiPriorityRule( + wifiNetworkPriority, + mWifiNetworkRecord, + selectedNetworkRecord, + carrierConfig == null + ? null + : new PersistableBundleWrapper(carrierConfig))); + } + + @Test + public void testMatchSelectedWifi() { + verifyMatchWifi( + true /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */); + } + + @Test + public void testMatchSelectedWifiBelowRssiThreshold() { + final PersistableBundle carrierConfig = new PersistableBundle(); + carrierConfig.putInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH); + carrierConfig.putInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH); + + verifyMatchWifi(true /* isSelectedNetwork */, carrierConfig, false /* expectMatch */); + } + + @Test + public void testMatchUnselectedWifi() { + verifyMatchWifi( + false /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */); + } + + @Test + public void testMatchUnselectedWifiBelowRssiThreshold() { + final PersistableBundle carrierConfig = new PersistableBundle(); + carrierConfig.putInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH); + + verifyMatchWifi(false /* isSelectedNetwork */, carrierConfig, false /* expectMatch */); + } + + private void verifyMatchWifiWithSsid(boolean useMatchedSsid, boolean expectMatch) { + final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER; + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .setSsids(Set.of(nwPrioritySsid)) + .build(); + + assertEquals( + expectMatch, + checkMatchesWifiPriorityRule( + wifiNetworkPriority, + mWifiNetworkRecord, + null /* currentlySelecetd */, + null /* carrierConfig */)); + } + + @Test + public void testMatchWifiWithSsid() { + verifyMatchWifiWithSsid(true /* useMatchedSsid */, true /* expectMatch */); + } + + @Test + public void testMatchWifiFailWithWrongSsid() { + verifyMatchWifiWithSsid(false /* useMatchedSsid */, false /* expectMatch */); + } + + private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() { + return new VcnCellUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS); + } + + @Test + public void testMatchMacroCell() { + assertTrue( + checkMatchesCellPriorityRule( + mVcnContext, + getCellNetworkPriorityBuilder().build(), + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchOpportunisticCell() { + final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority = + getCellNetworkPriorityBuilder().setOpportunistic(MATCH_REQUIRED).build(); + + when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true); + when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>()); + + assertTrue( + checkMatchesCellPriorityRule( + mVcnContext, + opportunisticCellNetworkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + private void verifyMatchMacroCellWithAllowedPlmnIds( + boolean useMatchedPlmnId, boolean expectMatch) { + final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER; + final VcnCellUnderlyingNetworkTemplate networkPriority = + getCellNetworkPriorityBuilder() + .setOperatorPlmnIds(Set.of(networkPriorityPlmnId)) + .build(); + + assertEquals( + expectMatch, + checkMatchesCellPriorityRule( + mVcnContext, + networkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchMacroCellWithAllowedPlmnIds() { + verifyMatchMacroCellWithAllowedPlmnIds(true /* useMatchedPlmnId */, true /* expectMatch */); + } + + @Test + public void testMatchMacroCellFailWithDisallowedPlmnIds() { + verifyMatchMacroCellWithAllowedPlmnIds( + false /* useMatchedPlmnId */, false /* expectMatch */); + } + + private void verifyMatchMacroCellWithAllowedSpecificCarrierIds( + boolean useMatchedCarrierId, boolean expectMatch) { + final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER; + final VcnCellUnderlyingNetworkTemplate networkPriority = + getCellNetworkPriorityBuilder() + .setSimSpecificCarrierIds(Set.of(networkPriorityCarrierId)) + .build(); + + assertEquals( + expectMatch, + checkMatchesCellPriorityRule( + mVcnContext, + networkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchMacroCellWithAllowedSpecificCarrierIds() { + verifyMatchMacroCellWithAllowedSpecificCarrierIds( + true /* useMatchedCarrierId */, true /* expectMatch */); + } + + @Test + public void testMatchMacroCellFailWithDisallowedSpecificCarrierIds() { + verifyMatchMacroCellWithAllowedSpecificCarrierIds( + false /* useMatchedCarrierId */, false /* expectMatch */); + } + + @Test + public void testMatchWifiFailWithoutNotRoamingBit() { + final VcnCellUnderlyingNetworkTemplate networkPriority = + getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build(); + + assertFalse( + checkMatchesCellPriorityRule( + mVcnContext, + networkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + private void verifyCalculatePriorityClass( + UnderlyingNetworkRecord networkRecord, int expectedIndex) { + final int priorityIndex = + calculatePriorityClass( + mVcnContext, + networkRecord, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES, + SUB_GROUP, + mSubscriptionSnapshot, + null /* currentlySelected */, + null /* carrierConfig */); + + assertEquals(expectedIndex, priorityIndex); + } + + @Test + public void testCalculatePriorityClass() throws Exception { + verifyCalculatePriorityClass(mCellNetworkRecord, 2); + } + + @Test + public void testCalculatePriorityClassFailToMatchAny() throws Exception { + final NetworkCapabilities nc = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSignalStrength(WIFI_RSSI_LOW) + .setSsid(SSID) + .build(); + final UnderlyingNetworkRecord wifiNetworkRecord = + new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */); + + verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java index 5af69b5d1bf2..fad9669911bb 100644 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.android.server.vcn; +package com.android.server.vcn.routeselection; import static com.android.server.vcn.VcnTestUtils.setupSystemService; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -40,6 +42,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; @@ -48,10 +51,11 @@ import android.telephony.TelephonyManager; import android.util.ArraySet; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkListener; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener; import org.junit.Before; import org.junit.Test; @@ -64,7 +68,7 @@ import java.util.Arrays; import java.util.Set; import java.util.UUID; -public class UnderlyingNetworkTrackerTest { +public class UnderlyingNetworkControllerTest { private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final int INITIAL_SUB_ID_1 = 1; private static final int INITIAL_SUB_ID_2 = 2; @@ -102,14 +106,14 @@ public class UnderlyingNetworkTrackerTest { @Mock private TelephonyManager mTelephonyManager; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; - @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; + @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb; @Mock private Network mNetwork; @Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor; private TestLooper mTestLooper; private VcnContext mVcnContext; - private UnderlyingNetworkTracker mUnderlyingNetworkTracker; + private UnderlyingNetworkController mUnderlyingNetworkController; @Before public void setUp() { @@ -140,12 +144,13 @@ public class UnderlyingNetworkTrackerTest { when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); - mUnderlyingNetworkTracker = - new UnderlyingNetworkTracker( + mUnderlyingNetworkController = + new UnderlyingNetworkController( mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(), SUB_GROUP, mSubscriptionSnapshot, - mNetworkTrackerCb); + mNetworkControllerCb); } private void resetVcnContext() { @@ -153,7 +158,8 @@ public class UnderlyingNetworkTrackerTest { doNothing().when(mVcnContext).ensureRunningOnLooperThread(); } - private static LinkProperties getLinkPropertiesWithName(String iface) { + // Package private for use in NetworkPriorityClassifierTest + static LinkProperties getLinkPropertiesWithName(String iface) { LinkProperties linkProperties = new LinkProperties(); linkProperties.setInterfaceName(iface); return linkProperties; @@ -181,11 +187,12 @@ public class UnderlyingNetworkTrackerTest { mVcnNetworkProvider, true /* isInTestMode */); - new UnderlyingNetworkTracker( + new UnderlyingNetworkController( vcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(), SUB_GROUP, mSubscriptionSnapshot, - mNetworkTrackerCb); + mNetworkControllerCb); verify(cm) .registerNetworkCallback( @@ -233,7 +240,7 @@ public class UnderlyingNetworkTrackerTest { mock(TelephonySubscriptionSnapshot.class); when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS); - mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate); + mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate); // verify that initially-filed bringup requests are unregistered (cell + wifi) verify(mConnectivityManager, times(INITIAL_SUB_IDS.size() + 3)) @@ -255,7 +262,7 @@ public class UnderlyingNetworkTrackerTest { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .setSubscriptionIds(netCapsSubIds) - .setSignalStrength(UnderlyingNetworkTracker.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) + .setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) .build(); } @@ -264,7 +271,7 @@ public class UnderlyingNetworkTrackerTest { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .setSubscriptionIds(netCapsSubIds) - .setSignalStrength(UnderlyingNetworkTracker.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) + .setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) .build(); } @@ -304,7 +311,7 @@ public class UnderlyingNetworkTrackerTest { @Test public void testTeardown() { - mUnderlyingNetworkTracker.teardown(); + mUnderlyingNetworkController.teardown(); // Expect 5 NetworkBringupCallbacks to be unregistered: 1 for WiFi, 2 for Cellular (1x for // each subId), and 1 for each of the Wifi signal strength thresholds @@ -348,6 +355,17 @@ public class UnderlyingNetworkTrackerTest { return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES); } + private static NetworkCapabilities buildResponseNwCaps( + NetworkCapabilities requestNetworkCaps, Set<Integer> netCapsSubIds) { + final TelephonyNetworkSpecifier telephonyNetworkSpecifier = + new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(netCapsSubIds.iterator().next()) + .build(); + return new NetworkCapabilities.Builder(requestNetworkCaps) + .setNetworkSpecifier(telephonyNetworkSpecifier) + .build(); + } + private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback( NetworkCapabilities networkCapabilities) { verify(mConnectivityManager) @@ -358,17 +376,20 @@ public class UnderlyingNetworkTrackerTest { UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.getValue(); cb.onAvailable(mNetwork); - cb.onCapabilitiesChanged(mNetwork, networkCapabilities); + + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(networkCapabilities, INITIAL_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES); cb.onBlockedStatusChanged(mNetwork, false /* isFalse */); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - networkCapabilities, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); return cb; } @@ -376,15 +397,17 @@ public class UnderlyingNetworkTrackerTest { public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - UPDATED_NETWORK_CAPABILITIES, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -396,29 +419,33 @@ public class UnderlyingNetworkTrackerTest { UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - INITIAL_NETWORK_CAPABILITIES, + buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), UPDATED_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test public void testRecordTrackerCallbackNotifiedForNetworkSuspended() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(SUSPENDED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - SUSPENDED_NETWORK_CAPABILITIES, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. - cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -426,19 +453,23 @@ public class UnderlyingNetworkTrackerTest { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES); - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - INITIAL_NETWORK_CAPABILITIES, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -450,10 +481,10 @@ public class UnderlyingNetworkTrackerTest { UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - INITIAL_NETWORK_CAPABILITIES, + buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), INITIAL_LINK_PROPERTIES, true /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -462,29 +493,31 @@ public class UnderlyingNetworkTrackerTest { cb.onLost(mNetwork); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null); } @Test public void testRecordTrackerCallbackIgnoresDuplicateRecord() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + cb.onCapabilitiesChanged( + mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS)); - // Verify no more calls to the UnderlyingNetworkTrackerCallback when the + // Verify no more calls to the UnderlyingNetworkControllerCallback when the // UnderlyingNetworkRecord does not actually change - verifyNoMoreInteractions(mNetworkTrackerCb); + verifyNoMoreInteractions(mNetworkControllerCb); } @Test public void testRecordTrackerCallbackNotifiedAfterTeardown() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - mUnderlyingNetworkTracker.teardown(); + mUnderlyingNetworkController.teardown(); - cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + cb.onCapabilitiesChanged( + mNetwork, buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, INITIAL_SUB_IDS)); // Verify that the only call was during onAvailable() - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); + verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); } // TODO (b/187991063): Add tests for network prioritization diff --git a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java index 29511f780bf6..e9e70783ebe9 100644 --- a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java +++ b/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java @@ -46,34 +46,85 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class MtuUtilsTest { + private void verifyUnderlyingMtuZero(boolean isIpv4) { + assertEquals( + IPV6_MIN_MTU, + getMtu(emptyList(), ETHER_MTU /* maxMtu */, 0 /* underlyingMtu */, isIpv4)); + } + + @Test + public void testUnderlyingMtuZeroV4() { + verifyUnderlyingMtuZero(true /* isIpv4 */); + } + @Test - public void testUnderlyingMtuZero() { + public void testUnderlyingMtuZeroV6() { + verifyUnderlyingMtuZero(false /* isIpv4 */); + } + + private void verifyClampsToMaxMtu(boolean isIpv4) { assertEquals( - IPV6_MIN_MTU, getMtu(emptyList(), ETHER_MTU /* maxMtu */, 0 /* underlyingMtu */)); + 0, getMtu(emptyList(), 0 /* maxMtu */, IPV6_MIN_MTU /* underlyingMtu */, isIpv4)); } @Test - public void testClampsToMaxMtu() { - assertEquals(0, getMtu(emptyList(), 0 /* maxMtu */, IPV6_MIN_MTU /* underlyingMtu */)); + public void testClampsToMaxMtuV4() { + verifyClampsToMaxMtu(true /* isIpv4 */); } @Test - public void testNormalModeAlgorithmLessThanUnderlyingMtu() { - final List<ChildSaProposal> saProposals = - Arrays.asList( - new ChildSaProposal.Builder() - .addEncryptionAlgorithm( - ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256) - .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) - .build()); + public void testClampsToMaxMtuV6() { + verifyClampsToMaxMtu(false /* isIpv4 */); + } + + private List<ChildSaProposal> buildChildSaProposalsWithNormalModeAlgo() { + return Arrays.asList( + new ChildSaProposal.Builder() + .addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256) + .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) + .build()); + } + private void verifyNormalModeAlgorithmLessThanUnderlyingMtu(boolean isIpv4) { final int actualMtu = - getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */); + getMtu( + buildChildSaProposalsWithNormalModeAlgo(), + ETHER_MTU /* maxMtu */, + ETHER_MTU /* underlyingMtu */, + isIpv4); assertTrue(ETHER_MTU > actualMtu); } @Test - public void testCombinedModeAlgorithmLessThanUnderlyingMtu() { + public void testNormalModeAlgorithmLessThanUnderlyingMtuV4() { + verifyNormalModeAlgorithmLessThanUnderlyingMtu(true /* isIpv4 */); + } + + @Test + public void testNormalModeAlgorithmLessThanUnderlyingMtuV6() { + verifyNormalModeAlgorithmLessThanUnderlyingMtu(false /* isIpv4 */); + } + + @Test + public void testMtuIpv4LessThanMtuIpv6() { + final int actualMtuV4 = + getMtu( + buildChildSaProposalsWithNormalModeAlgo(), + ETHER_MTU /* maxMtu */, + ETHER_MTU /* underlyingMtu */, + true /* isIpv4 */); + + final int actualMtuV6 = + getMtu( + buildChildSaProposalsWithNormalModeAlgo(), + ETHER_MTU /* maxMtu */, + ETHER_MTU /* underlyingMtu */, + false /* isIpv4 */); + + assertTrue(actualMtuV4 < actualMtuV6); + } + + private void verifyCombinedModeAlgorithmLessThanUnderlyingMtu(boolean isIpv4) { final List<ChildSaProposal> saProposals = Arrays.asList( new ChildSaProposal.Builder() @@ -86,7 +137,17 @@ public class MtuUtilsTest { .build()); final int actualMtu = - getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */); + getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */, isIpv4); assertTrue(ETHER_MTU > actualMtu); } + + @Test + public void testCombinedModeAlgorithmLessThanUnderlyingMtuV4() { + verifyCombinedModeAlgorithmLessThanUnderlyingMtu(true /* isIpv4 */); + } + + @Test + public void testCombinedModeAlgorithmLessThanUnderlyingMtuV6() { + verifyCombinedModeAlgorithmLessThanUnderlyingMtu(false /* isIpv4 */); + } } diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java index a44a734a2dce..9c6d85238b77 100644 --- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java +++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java @@ -18,6 +18,8 @@ package com.android.server.vcn.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.os.PersistableBundle; @@ -211,4 +213,93 @@ public class PersistableBundleUtilsTest { assertEquals(testInt, result); } + + private PersistableBundle getTestBundle() { + final PersistableBundle bundle = new PersistableBundle(); + + bundle.putBoolean(TEST_KEY + "Boolean", true); + bundle.putBooleanArray(TEST_KEY + "BooleanArray", new boolean[] {true, false}); + bundle.putDouble(TEST_KEY + "Double", 0.1); + bundle.putDoubleArray(TEST_KEY + "DoubleArray", new double[] {0.1, 0.2, 0.3}); + bundle.putInt(TEST_KEY + "Int", 1); + bundle.putIntArray(TEST_KEY + "IntArray", new int[] {1, 2}); + bundle.putLong(TEST_KEY + "Long", 5L); + bundle.putLongArray(TEST_KEY + "LongArray", new long[] {0L, -1L, -2L}); + bundle.putString(TEST_KEY + "String", "TEST"); + bundle.putStringArray(TEST_KEY + "StringArray", new String[] {"foo", "bar", "bas"}); + bundle.putPersistableBundle( + TEST_KEY + "PersistableBundle", + new TestClass(1, TEST_INT_ARRAY, TEST_STRING_PREFIX, new PersistableBundle()) + .toPersistableBundle()); + + return bundle; + } + + @Test + public void testMinimizeBundle() throws Exception { + final String[] minimizedKeys = + new String[] { + TEST_KEY + "Boolean", + TEST_KEY + "BooleanArray", + TEST_KEY + "Double", + TEST_KEY + "DoubleArray", + TEST_KEY + "Int", + TEST_KEY + "IntArray", + TEST_KEY + "Long", + TEST_KEY + "LongArray", + TEST_KEY + "String", + TEST_KEY + "StringArray", + TEST_KEY + "PersistableBundle" + }; + + final PersistableBundle testBundle = getTestBundle(); + testBundle.putBoolean(TEST_KEY + "Boolean2", true); + + final PersistableBundle minimized = + PersistableBundleUtils.minimizeBundle(testBundle, minimizedKeys); + + // Verify that the minimized bundle is NOT the same in size OR values due to the extra + // Boolean2 key + assertFalse(PersistableBundleUtils.isEqual(testBundle, minimized)); + + // Verify that removing the extra key from the source bundle results in equality. + testBundle.remove(TEST_KEY + "Boolean2"); + assertTrue(PersistableBundleUtils.isEqual(testBundle, minimized)); + } + + @Test + public void testToFromDiskStableBytes() throws Exception { + final PersistableBundle testBundle = getTestBundle(); + final PersistableBundle result = + PersistableBundleUtils.fromDiskStableBytes( + PersistableBundleUtils.toDiskStableBytes(testBundle)); + assertTrue(PersistableBundleUtils.isEqual(testBundle, result)); + } + + @Test + public void testEquality_identical() throws Exception { + final PersistableBundle left = getTestBundle(); + final PersistableBundle right = getTestBundle(); + + assertTrue(PersistableBundleUtils.isEqual(left, right)); + } + + @Test + public void testEquality_different() throws Exception { + final PersistableBundle left = getTestBundle(); + final PersistableBundle right = getTestBundle(); + + left.putBoolean(TEST_KEY + "Boolean2", true); + assertFalse(PersistableBundleUtils.isEqual(left, right)); + + left.remove(TEST_KEY + "Boolean2"); + assertTrue(PersistableBundleUtils.isEqual(left, right)); + } + + @Test + public void testEquality_null() throws Exception { + assertFalse(PersistableBundleUtils.isEqual(getTestBundle(), null)); + assertFalse(PersistableBundleUtils.isEqual(null, getTestBundle())); + assertTrue(PersistableBundleUtils.isEqual(null, null)); + } } |