• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.pixel.utils;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.SystemClock;
24 import android.support.test.uiautomator.By;
25 import android.support.test.uiautomator.UiDevice;
26 import android.util.Log;
27 
28 import org.junit.Assert;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.nio.file.Paths;
33 
34 public class DeviceUtils {
35     private static final String TAG = DeviceUtils.class.getSimpleName();
36     private static final String LOG_DATA_DIR = "/sdcard/logData";
37     private static final int MAX_RECORDING_PARTS = 5;
38     private static final long WAIT_ONE_SECOND_IN_MS = 1000;
39     private static final long VIDEO_TAIL_BUFFER = 500;
40     private static final String DISMISS_KEYGUARD = "wm dismiss-keyguard";
41 
42     private RecordingThread mCurrentThread;
43     private File mLogDataDir;
44     private UiDevice mDevice;
45 
DeviceUtils(UiDevice device)46     public DeviceUtils(UiDevice device) {
47         mDevice = device;
48     }
49 
50     /** Create a directory to save test screenshots, screenrecord and text files. */
createLogDataDir()51     public void createLogDataDir() {
52         mLogDataDir = new File(LOG_DATA_DIR);
53         if (mLogDataDir.exists()) {
54             String[] children = mLogDataDir.list();
55             for (String file : children) {
56                 new File(mLogDataDir, file).delete();
57             }
58         } else {
59             mLogDataDir.mkdirs();
60         }
61     }
62 
63     /** Wake up the device and dismiss the keyguard. */
wakeAndUnlockScreen()64     public void wakeAndUnlockScreen() throws Exception {
65         mDevice.wakeUp();
66         SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
67         mDevice.executeShellCommand(DISMISS_KEYGUARD);
68         SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
69     }
70 
71     /**
72      * Go back to home screen by pressing back key five times and home key to avoid the infinite
73      * loop since some apps' activities cannot be exited to home screen by back key event.
74      */
backToHome(String launcherPkg)75     public void backToHome(String launcherPkg) {
76         for (int i = 0; i < 5; i++) {
77             mDevice.pressBack();
78             mDevice.waitForIdle();
79             if (mDevice.hasObject(By.pkg(launcherPkg))) {
80                 break;
81             }
82         }
83         mDevice.pressHome();
84     }
85 
86     /**
87      * Launch an app with the given package name
88      *
89      * @param packageName Name of package to be launched
90      */
launchApp(String packageName)91     public void launchApp(String packageName) {
92         Context context = getInstrumentation().getContext();
93         Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
94         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
95         context.startActivity(intent);
96     }
97 
98     /**
99      * Take a screenshot on the device and save it in {@code logDataDir}.
100      *
101      * @param packageName The package name of 3P apps screenshotted.
102      * @param description The description of actions or operations on the device.
103      */
takeScreenshot(String packageName, String description)104     public void takeScreenshot(String packageName, String description) {
105         File screenshot =
106                 new File(
107                         LOG_DATA_DIR,
108                         String.format("%s_screenshot_%s.png", packageName, description));
109         mDevice.takeScreenshot(screenshot);
110     }
111 
112     /**
113      * Start the screen recording.
114      *
115      * @param packageName The package name of 3P apps screenrecorded.
116      */
startRecording(String packageName)117     public void startRecording(String packageName) {
118         Log.v(TAG, "Started Recording");
119         mCurrentThread =
120                 new RecordingThread(
121                         "test-screen-record", String.format("%s_screenrecord", packageName));
122         mCurrentThread.start();
123     }
124 
125     /** Stop already started screen recording. */
stopRecording()126     public void stopRecording() {
127         // Skip if not directory.
128         if (mLogDataDir == null) {
129             return;
130         }
131         // Add some extra time to the video end.
132         SystemClock.sleep(VIDEO_TAIL_BUFFER);
133         // Ctrl + C all screen record processes.
134         mCurrentThread.cancel();
135         // Wait for the thread to completely die.
136         try {
137             mCurrentThread.join();
138         } catch (InterruptedException ex) {
139             Log.e(TAG, "Interrupted when joining the recording thread.", ex);
140         }
141         Log.v(TAG, "Stopped Recording");
142     }
143 
144     /** Returns the recording's name for {@code part} of launch description. */
getOutputFile(String description, int part)145     public File getOutputFile(String description, int part) {
146         // Omit the iteration number for the first iteration.
147         final String fileName = String.format("%s-video%s.mp4", description, part == 1 ? "" : part);
148         return Paths.get(mLogDataDir.getAbsolutePath(), fileName).toFile();
149     }
150 
151     /**
152      * Encapsulates the start and stop screen recording logic. Copied from ScreenRecordCollector.
153      */
154     private class RecordingThread extends Thread {
155         private final String mDescription;
156 
157         private boolean mContinue;
158 
RecordingThread(String name, String description)159         RecordingThread(String name, String description) {
160             super(name);
161 
162             mContinue = true;
163 
164             Assert.assertNotNull("No test description provided for recording.", description);
165             mDescription = description;
166         }
167 
168         @Override
run()169         public void run() {
170             try {
171                 // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc.
172                 for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) {
173                     File output = getOutputFile(mDescription, i);
174                     Log.d(TAG, String.format("Recording screen to %s", output.getAbsolutePath()));
175                     // Make sure not to block on this background command in the main thread so
176                     // that the test continues to run, but block in this thread so it does not
177                     // trigger a new screen recording session before the prior one completes.
178                     mDevice.executeShellCommand(
179                             String.format("screenrecord %s", output.getAbsolutePath()));
180                 }
181             } catch (IOException e) {
182                 throw new RuntimeException("Caught exception while screen recording.");
183             }
184         }
185 
cancel()186         public void cancel() {
187             mContinue = false;
188             // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each.
189             try {
190                 String[] pids = mDevice.executeShellCommand("pidof screenrecord").split(" ");
191                 for (String pid : pids) {
192                     // Avoid empty process ids, because of weird splitting behavior.
193                     if (pid.isEmpty()) {
194                         continue;
195                     }
196                     mDevice.executeShellCommand(String.format("kill -2 %s", pid));
197                     Log.d(TAG, String.format("Sent SIGINT 2 to screenrecord process (%s)", pid));
198                 }
199             } catch (IOException e) {
200                 throw new RuntimeException("Failed to kill screen recording process.");
201             }
202         }
203     }
204 }
205