1 /*
2  * Copyright (C) 2019 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 android.wm;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import android.app.Activity;
22 import android.app.KeyguardManager;
23 import android.app.UiAutomation;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.BatteryManager;
27 import android.os.ParcelFileDescriptor;
28 import android.os.PowerManager;
29 import android.perftests.utils.PerfTestActivity;
30 import android.provider.Settings;
31 
32 import androidx.test.rule.ActivityTestRule;
33 import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
34 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
35 import androidx.test.runner.lifecycle.Stage;
36 
37 import org.junit.AfterClass;
38 import org.junit.BeforeClass;
39 import org.junit.runner.Description;
40 import org.junit.runners.model.Statement;
41 
42 import java.io.ByteArrayOutputStream;
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.util.concurrent.TimeUnit;
47 
48 public class WindowManagerPerfTestBase {
49     static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
50     static final long NANOS_PER_S = 1000L * 1000 * 1000;
51     static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
52     static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S;
53 
54     /**
55      * The out directory matching the directory-keys of collector in AndroidTest.xml. The directory
56      * is in /data because while enabling method profling of system server, it cannot write the
57      * trace to external storage.
58      */
59     static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests");
60 
61     private static int sOriginalStayOnWhilePluggedIn;
62 
63     @BeforeClass
setUpOnce()64     public static void setUpOnce() {
65         final Context context = getInstrumentation().getContext();
66         final int stayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(),
67                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
68         sOriginalStayOnWhilePluggedIn = -1;
69         if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) {
70             sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn;
71             // Keep the device awake during testing.
72             setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY);
73         }
74 
75         if (!BASE_OUT_PATH.exists()) {
76             executeShellCommand("mkdir -p " + BASE_OUT_PATH);
77         }
78         if (!context.getSystemService(PowerManager.class).isInteractive()
79                 || context.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
80             executeShellCommand("input keyevent KEYCODE_WAKEUP");
81             executeShellCommand("wm dismiss-keyguard");
82         }
83         context.startActivity(new Intent(Intent.ACTION_MAIN)
84                 .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
85     }
86 
87     @AfterClass
tearDownOnce()88     public static void tearDownOnce() {
89         if (sOriginalStayOnWhilePluggedIn != -1) {
90             setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn);
91         }
92     }
93 
setStayOnWhilePluggedIn(int value)94     private static void setStayOnWhilePluggedIn(int value) {
95         executeShellCommand(String.format("settings put global %s %d",
96                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value));
97     }
98 
99     /**
100      * Executes shell command with reading the output. It may also used to block until the current
101      * command is completed.
102      */
executeShellCommand(String command)103     static ByteArrayOutputStream executeShellCommand(String command) {
104         final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand(command);
105         final byte[] buf = new byte[512];
106         final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
107         int bytesRead;
108         try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
109             while ((bytesRead = fis.read(buf)) != -1) {
110                 bytes.write(buf, 0, bytesRead);
111             }
112         } catch (IOException e) {
113             throw new RuntimeException(e);
114         }
115         return bytes;
116     }
117 
118     /** Starts method tracing on system server. */
startProfiling(String subPath)119     void startProfiling(String subPath) {
120         executeShellCommand("am profile start system " + new File(BASE_OUT_PATH, subPath));
121     }
122 
stopProfiling()123     void stopProfiling() {
124         executeShellCommand("am profile stop system");
125     }
126 
127     /**
128      * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage.
129      */
130     static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
131         private final Intent mStartIntent =
132                 new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class);
133         private final LifecycleListener mLifecycleListener = new LifecycleListener();
134 
PerfTestActivityRule()135         PerfTestActivityRule() {
136             this(false /* launchActivity */);
137         }
138 
PerfTestActivityRule(boolean launchActivity)139         PerfTestActivityRule(boolean launchActivity) {
140             super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
141         }
142 
143         @Override
apply(Statement base, Description description)144         public Statement apply(Statement base, Description description) {
145             final Statement wrappedStatement = new Statement() {
146                 @Override
147                 public void evaluate() throws Throwable {
148                     ActivityLifecycleMonitorRegistry.getInstance()
149                             .addLifecycleCallback(mLifecycleListener);
150                     base.evaluate();
151                     ActivityLifecycleMonitorRegistry.getInstance()
152                             .removeLifecycleCallback(mLifecycleListener);
153                 }
154             };
155             return super.apply(wrappedStatement, description);
156         }
157 
158         @Override
getActivityIntent()159         protected Intent getActivityIntent() {
160             return mStartIntent;
161         }
162 
163         @Override
launchActivity(Intent intent)164         public PerfTestActivity launchActivity(Intent intent) {
165             final PerfTestActivity activity = super.launchActivity(intent);
166             mLifecycleListener.setTargetActivity(activity);
167             return activity;
168         }
169 
launchActivity()170         PerfTestActivity launchActivity() {
171             return launchActivity(mStartIntent);
172         }
173 
waitForIdleSync(Stage state)174         void waitForIdleSync(Stage state) {
175             mLifecycleListener.waitForIdleSync(state);
176         }
177     }
178 
179     static class LifecycleListener implements ActivityLifecycleCallback {
180         private Activity mTargetActivity;
181         private Stage mWaitingStage;
182         private Stage mReceivedStage;
183 
setTargetActivity(Activity activity)184         void setTargetActivity(Activity activity) {
185             mTargetActivity = activity;
186             mReceivedStage = mWaitingStage = null;
187         }
188 
waitForIdleSync(Stage stage)189         void waitForIdleSync(Stage stage) {
190             synchronized (this) {
191                 if (stage != mReceivedStage) {
192                     mWaitingStage = stage;
193                     try {
194                         wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
195                     } catch (InterruptedException impossible) { }
196                 }
197                 mWaitingStage = mReceivedStage = null;
198             }
199             getInstrumentation().waitForIdleSync();
200         }
201 
202         @Override
onActivityLifecycleChanged(Activity activity, Stage stage)203         public void onActivityLifecycleChanged(Activity activity, Stage stage) {
204             if (mTargetActivity != activity) {
205                 return;
206             }
207 
208             synchronized (this) {
209                 mReceivedStage = stage;
210                 if (mWaitingStage == mReceivedStage) {
211                     notifyAll();
212                 }
213             }
214         }
215     }
216 }
217