Loading tests/AppLaunch/Android.bp +3 −1 Original line number Original line Diff line number Diff line Loading @@ -8,6 +8,8 @@ android_test { "android.test.base", "android.test.base", "android.test.runner", "android.test.runner", ], ], static_libs: ["androidx.test.rules"], static_libs: [ "androidx.test.rules", "ub-uiautomator"], test_suites: ["device-tests"], test_suites: ["device-tests"], } } tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +150 −1 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ */ package com.android.tests.applaunch; package com.android.tests.applaunch; import static org.junit.Assert.assertNotNull; import android.accounts.Account; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManager; import android.app.ActivityManager; import android.app.ActivityManager; Loading @@ -29,7 +31,9 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserHandle; import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestRunner; import android.util.Log; import android.util.Log; Loading @@ -46,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.OutputStreamWriter; import java.nio.file.Paths; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter; import java.time.ZonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneOffset; import java.time.ZoneOffset; Loading @@ -67,6 +72,7 @@ import java.util.Set; * in the following format: * in the following format: * -e apps <app name>^<result key>|<app name>^<result key> * -e apps <app name>^<result key>|<app name>^<result key> */ */ @Deprecated public class AppLaunch extends InstrumentationTestCase { public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; private static final int JOIN_TIMEOUT = 10000; Loading Loading @@ -94,6 +100,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; private static final String ENABLE_SCREEN_RECORDING = "enable_screen_recording"; private static final int MAX_RECORDING_PARTS = 5; private static final long VIDEO_TAIL_BUFFER = 500; private static final String SIMPLEPERF_APP_CMD = private static final String SIMPLEPERF_APP_CMD = "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; Loading Loading @@ -144,14 +153,17 @@ public class AppLaunch extends InstrumentationTestCase { private Map<String, Intent> mNameToIntent; private Map<String, Intent> mNameToIntent; private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); private RecordingThread mCurrentThread; private Map<String, String> mNameToResultKey; private Map<String, String> mNameToResultKey; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private IActivityManager mAm; private IActivityManager mAm; private File launchSubDir = null; private String mSimplePerfCmd = null; private String mSimplePerfCmd = null; private String mLaunchOrder = null; private String mLaunchOrder = null; private boolean mDropCache = false; private boolean mDropCache = false; private int mLaunchIterations = 10; private int mLaunchIterations = 10; private boolean mForceStopApp = true; private boolean mForceStopApp = true; private boolean mEnableRecording = false; private int mTraceLaunchCount = 0; private int mTraceLaunchCount = 0; private String mTraceDirectoryStr = null; private String mTraceDirectoryStr = null; private Bundle mResult = new Bundle(); private Bundle mResult = new Bundle(); Loading @@ -166,6 +178,7 @@ public class AppLaunch extends InstrumentationTestCase { private boolean mCycleCleanUp = false; private boolean mCycleCleanUp = false; private boolean mTraceAll = false; private boolean mTraceAll = false; private boolean mIterationCycle = false; private boolean mIterationCycle = false; private UiDevice mDevice; enum IorapStatus { enum IorapStatus { UNDEFINED, UNDEFINED, Loading Loading @@ -222,7 +235,7 @@ public class AppLaunch extends InstrumentationTestCase { } } try { try { File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { throw new IOException("Unable to create the lauch file sub directory " throw new IOException("Unable to create the lauch file sub directory " Loading Loading @@ -923,9 +936,16 @@ public class AppLaunch extends InstrumentationTestCase { mLaunchIterations = Integer.parseInt(launchIterations); mLaunchIterations = Integer.parseInt(launchIterations); } } String forceStopApp = args.getString(KEY_FORCE_STOP_APP); String forceStopApp = args.getString(KEY_FORCE_STOP_APP); if (forceStopApp != null) { if (forceStopApp != null) { mForceStopApp = Boolean.parseBoolean(forceStopApp); mForceStopApp = Boolean.parseBoolean(forceStopApp); } } String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); if (enableRecording != null) { mEnableRecording = Boolean.parseBoolean(enableRecording); } String appList = args.getString(KEY_APPS); String appList = args.getString(KEY_APPS); if (appList == null) if (appList == null) return; return; Loading Loading @@ -1038,6 +1058,9 @@ public class AppLaunch extends InstrumentationTestCase { private AppLaunchResult startApp(String appName, String launchReason) private AppLaunchResult startApp(String appName, String launchReason) throws NameNotFoundException, RemoteException { throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); Log.i(TAG, "Starting " + appName); if(mEnableRecording) { startRecording(appName, launchReason); } Intent startIntent = mNameToIntent.get(appName); Intent startIntent = mNameToIntent.get(appName); if (startIntent == null) { if (startIntent == null) { Loading @@ -1053,6 +1076,10 @@ public class AppLaunch extends InstrumentationTestCase { } catch (InterruptedException e) { } catch (InterruptedException e) { // ignore // ignore } } if(mEnableRecording) { stopRecording(); } return runnable.getResult(); return runnable.getResult(); } } Loading Loading @@ -1360,4 +1387,126 @@ public class AppLaunch extends InstrumentationTestCase { } } } } /** * Start the screen recording while launching the app. * * @param appName * @param launchReason */ private void startRecording(String appName, String launchReason) { Log.v(TAG, "Started Recording"); mCurrentThread = new RecordingThread("test-screen-record", String.format("%s_%s", appName, launchReason)); mCurrentThread.start(); } /** * Stop already started screen recording. */ private void stopRecording() { // Skip if not directory. if (launchSubDir == null) { return; } // Add some extra time to the video end. SystemClock.sleep(VIDEO_TAIL_BUFFER); // Ctrl + C all screen record processes. mCurrentThread.cancel(); // Wait for the thread to completely die. try { mCurrentThread.join(); } catch (InterruptedException ex) { Log.e(TAG, "Interrupted when joining the recording thread.", ex); } Log.v(TAG, "Stopped Recording"); } /** Returns the recording's name for part {@code part} of launch description. */ private File getOutputFile(String description, int part) { // Omit the iteration number for the first iteration. final String fileName = String.format( "%s-video%s.mp4", description, part == 1 ? "" : part); return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); } /** * Encapsulates the start and stop screen recording logic. * Copied from ScreenRecordCollector. */ private class RecordingThread extends Thread { private final String mDescription; private final List<File> mRecordings; private boolean mContinue; public RecordingThread(String name, String description) { super(name); mContinue = true; mRecordings = new ArrayList<>(); assertNotNull("No test description provided for recording.", description); mDescription = description; } @Override public void run() { try { // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { File output = getOutputFile(mDescription, i); Log.d( TAG, String.format("Recording screen to %s", output.getAbsolutePath())); mRecordings.add(output); // Make sure not to block on this background command in the main thread so // that the test continues to run, but block in this thread so it does not // trigger a new screen recording session before the prior one completes. getDevice().executeShellCommand( String.format("screenrecord %s", output.getAbsolutePath())); } } catch (IOException e) { throw new RuntimeException("Caught exception while screen recording."); } } public void cancel() { mContinue = false; // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. try { String[] pids = getDevice().executeShellCommand( "pidof screenrecord").split(" "); for (String pid : pids) { // Avoid empty process ids, because of weird splitting behavior. if (pid.isEmpty()) { continue; } getDevice().executeShellCommand( String.format("kill -2 %s", pid)); Log.d( TAG, String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); } } catch (IOException e) { throw new RuntimeException("Failed to kill screen recording process."); } } public List<File> getRecordings() { return mRecordings; } } public UiDevice getDevice() { if (mDevice == null) { mDevice = UiDevice.getInstance(getInstrumentation()); } return mDevice; } } } Loading
tests/AppLaunch/Android.bp +3 −1 Original line number Original line Diff line number Diff line Loading @@ -8,6 +8,8 @@ android_test { "android.test.base", "android.test.base", "android.test.runner", "android.test.runner", ], ], static_libs: ["androidx.test.rules"], static_libs: [ "androidx.test.rules", "ub-uiautomator"], test_suites: ["device-tests"], test_suites: ["device-tests"], } }
tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +150 −1 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ */ package com.android.tests.applaunch; package com.android.tests.applaunch; import static org.junit.Assert.assertNotNull; import android.accounts.Account; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManager; import android.app.ActivityManager; import android.app.ActivityManager; Loading @@ -29,7 +31,9 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserHandle; import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestRunner; import android.util.Log; import android.util.Log; Loading @@ -46,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.OutputStreamWriter; import java.nio.file.Paths; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter; import java.time.ZonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneOffset; import java.time.ZoneOffset; Loading @@ -67,6 +72,7 @@ import java.util.Set; * in the following format: * in the following format: * -e apps <app name>^<result key>|<app name>^<result key> * -e apps <app name>^<result key>|<app name>^<result key> */ */ @Deprecated public class AppLaunch extends InstrumentationTestCase { public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; private static final int JOIN_TIMEOUT = 10000; Loading Loading @@ -94,6 +100,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; private static final String ENABLE_SCREEN_RECORDING = "enable_screen_recording"; private static final int MAX_RECORDING_PARTS = 5; private static final long VIDEO_TAIL_BUFFER = 500; private static final String SIMPLEPERF_APP_CMD = private static final String SIMPLEPERF_APP_CMD = "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; Loading Loading @@ -144,14 +153,17 @@ public class AppLaunch extends InstrumentationTestCase { private Map<String, Intent> mNameToIntent; private Map<String, Intent> mNameToIntent; private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); private RecordingThread mCurrentThread; private Map<String, String> mNameToResultKey; private Map<String, String> mNameToResultKey; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private IActivityManager mAm; private IActivityManager mAm; private File launchSubDir = null; private String mSimplePerfCmd = null; private String mSimplePerfCmd = null; private String mLaunchOrder = null; private String mLaunchOrder = null; private boolean mDropCache = false; private boolean mDropCache = false; private int mLaunchIterations = 10; private int mLaunchIterations = 10; private boolean mForceStopApp = true; private boolean mForceStopApp = true; private boolean mEnableRecording = false; private int mTraceLaunchCount = 0; private int mTraceLaunchCount = 0; private String mTraceDirectoryStr = null; private String mTraceDirectoryStr = null; private Bundle mResult = new Bundle(); private Bundle mResult = new Bundle(); Loading @@ -166,6 +178,7 @@ public class AppLaunch extends InstrumentationTestCase { private boolean mCycleCleanUp = false; private boolean mCycleCleanUp = false; private boolean mTraceAll = false; private boolean mTraceAll = false; private boolean mIterationCycle = false; private boolean mIterationCycle = false; private UiDevice mDevice; enum IorapStatus { enum IorapStatus { UNDEFINED, UNDEFINED, Loading Loading @@ -222,7 +235,7 @@ public class AppLaunch extends InstrumentationTestCase { } } try { try { File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { throw new IOException("Unable to create the lauch file sub directory " throw new IOException("Unable to create the lauch file sub directory " Loading Loading @@ -923,9 +936,16 @@ public class AppLaunch extends InstrumentationTestCase { mLaunchIterations = Integer.parseInt(launchIterations); mLaunchIterations = Integer.parseInt(launchIterations); } } String forceStopApp = args.getString(KEY_FORCE_STOP_APP); String forceStopApp = args.getString(KEY_FORCE_STOP_APP); if (forceStopApp != null) { if (forceStopApp != null) { mForceStopApp = Boolean.parseBoolean(forceStopApp); mForceStopApp = Boolean.parseBoolean(forceStopApp); } } String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); if (enableRecording != null) { mEnableRecording = Boolean.parseBoolean(enableRecording); } String appList = args.getString(KEY_APPS); String appList = args.getString(KEY_APPS); if (appList == null) if (appList == null) return; return; Loading Loading @@ -1038,6 +1058,9 @@ public class AppLaunch extends InstrumentationTestCase { private AppLaunchResult startApp(String appName, String launchReason) private AppLaunchResult startApp(String appName, String launchReason) throws NameNotFoundException, RemoteException { throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); Log.i(TAG, "Starting " + appName); if(mEnableRecording) { startRecording(appName, launchReason); } Intent startIntent = mNameToIntent.get(appName); Intent startIntent = mNameToIntent.get(appName); if (startIntent == null) { if (startIntent == null) { Loading @@ -1053,6 +1076,10 @@ public class AppLaunch extends InstrumentationTestCase { } catch (InterruptedException e) { } catch (InterruptedException e) { // ignore // ignore } } if(mEnableRecording) { stopRecording(); } return runnable.getResult(); return runnable.getResult(); } } Loading Loading @@ -1360,4 +1387,126 @@ public class AppLaunch extends InstrumentationTestCase { } } } } /** * Start the screen recording while launching the app. * * @param appName * @param launchReason */ private void startRecording(String appName, String launchReason) { Log.v(TAG, "Started Recording"); mCurrentThread = new RecordingThread("test-screen-record", String.format("%s_%s", appName, launchReason)); mCurrentThread.start(); } /** * Stop already started screen recording. */ private void stopRecording() { // Skip if not directory. if (launchSubDir == null) { return; } // Add some extra time to the video end. SystemClock.sleep(VIDEO_TAIL_BUFFER); // Ctrl + C all screen record processes. mCurrentThread.cancel(); // Wait for the thread to completely die. try { mCurrentThread.join(); } catch (InterruptedException ex) { Log.e(TAG, "Interrupted when joining the recording thread.", ex); } Log.v(TAG, "Stopped Recording"); } /** Returns the recording's name for part {@code part} of launch description. */ private File getOutputFile(String description, int part) { // Omit the iteration number for the first iteration. final String fileName = String.format( "%s-video%s.mp4", description, part == 1 ? "" : part); return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); } /** * Encapsulates the start and stop screen recording logic. * Copied from ScreenRecordCollector. */ private class RecordingThread extends Thread { private final String mDescription; private final List<File> mRecordings; private boolean mContinue; public RecordingThread(String name, String description) { super(name); mContinue = true; mRecordings = new ArrayList<>(); assertNotNull("No test description provided for recording.", description); mDescription = description; } @Override public void run() { try { // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { File output = getOutputFile(mDescription, i); Log.d( TAG, String.format("Recording screen to %s", output.getAbsolutePath())); mRecordings.add(output); // Make sure not to block on this background command in the main thread so // that the test continues to run, but block in this thread so it does not // trigger a new screen recording session before the prior one completes. getDevice().executeShellCommand( String.format("screenrecord %s", output.getAbsolutePath())); } } catch (IOException e) { throw new RuntimeException("Caught exception while screen recording."); } } public void cancel() { mContinue = false; // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. try { String[] pids = getDevice().executeShellCommand( "pidof screenrecord").split(" "); for (String pid : pids) { // Avoid empty process ids, because of weird splitting behavior. if (pid.isEmpty()) { continue; } getDevice().executeShellCommand( String.format("kill -2 %s", pid)); Log.d( TAG, String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); } } catch (IOException e) { throw new RuntimeException("Failed to kill screen recording process."); } } public List<File> getRecordings() { return mRecordings; } } public UiDevice getDevice() { if (mDevice == null) { mDevice = UiDevice.getInstance(getInstrumentation()); } return mDevice; } } }