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