| /* |
| * Copyright (C) 2015 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.platform.systemui.tests.jank; |
| |
| import static android.support.test.InstrumentationRegistry.getInstrumentation; |
| import static android.system.helpers.OverviewHelper.isRecentsInLauncher; |
| |
| import static org.junit.Assert.assertNotNull; |
| |
| import android.app.Notification.Action; |
| import android.app.Notification.Builder; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.RemoteInput; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Icon; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.jank.GfxMonitor; |
| import android.support.test.jank.JankTest; |
| import android.support.test.jank.JankTestBase; |
| import android.support.test.timeresulthelper.TimeResultLogger; |
| import android.support.test.uiautomator.By; |
| import android.support.test.uiautomator.BySelector; |
| import android.support.test.uiautomator.Direction; |
| import android.support.test.uiautomator.UiDevice; |
| import android.support.test.uiautomator.UiObject; |
| import android.support.test.uiautomator.UiObject2; |
| import android.support.test.uiautomator.UiSelector; |
| import android.support.test.uiautomator.Until; |
| import android.system.helpers.LockscreenHelper; |
| import android.system.helpers.OverviewHelper; |
| import android.widget.Button; |
| import android.widget.ImageView; |
| |
| import com.android.internal.hardware.AmbientDisplayConfiguration; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public class SystemUiJankTests extends JankTestBase { |
| |
| private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; |
| private static final String SETTINGS_PACKAGE = "com.android.settings"; |
| private static final BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view"); |
| private static final String LOG_TAG = SystemUiJankTests.class.getSimpleName(); |
| private static final int SWIPE_MARGIN = 5; |
| private static final int DEFAULT_SCROLL_STEPS = 15; |
| private static final int BRIGHTNESS_SCROLL_STEPS = 30; |
| private static final int DEFAULT_FLING_SPEED = 15000; |
| |
| // short transitions should be repeated within the test function, otherwise frame stats |
| // captured are not really meaningful in a statistical sense |
| private static final int INNER_LOOP = 3; |
| private static final int[] ICONS = new int[] { |
| android.R.drawable.stat_notify_call_mute, |
| android.R.drawable.stat_notify_chat, |
| android.R.drawable.stat_notify_error, |
| android.R.drawable.stat_notify_missed_call, |
| android.R.drawable.stat_notify_more, |
| android.R.drawable.stat_notify_sdcard, |
| android.R.drawable.stat_notify_sdcard_prepare, |
| android.R.drawable.stat_notify_sdcard_usb, |
| android.R.drawable.stat_notify_sync, |
| android.R.drawable.stat_notify_sync_noanim, |
| android.R.drawable.stat_notify_voicemail, |
| }; |
| private static final String NOTIFICATION_TEXT = "Lorem ipsum dolor sit amet"; |
| private static final String REPLY_TEXT = "Reply"; |
| private static final File TIMESTAMP_FILE = new File(Environment.getExternalStorageDirectory() |
| .getAbsolutePath(), "autotester.log"); |
| private static final File RESULTS_FILE = new File(Environment.getExternalStorageDirectory() |
| .getAbsolutePath(), "results.log"); |
| private static final String GMAIL_PACKAGE_NAME = "com.google.android.gm"; |
| private static final String DISABLE_COMMAND = "pm disable-user "; |
| private static final String ENABLE_COMMAND = "pm enable "; |
| private static final String PULSE_COMMAND = "am broadcast -a com.android.systemui.doze.pulse"; |
| private static final String PIN = "1234"; |
| |
| /** |
| * Group mode: Let the system auto-group our notifications. This is required so we don't screw |
| * up jank numbers for our existing notification list pull test. |
| */ |
| private static final int GROUP_MODE_LEGACY = 0; |
| |
| /** |
| * Group mode: Group the notifications. |
| */ |
| private static final int GROUP_MODE_GROUPED = 1; |
| |
| /** |
| * Group mode: All notifications should be separate |
| */ |
| private static final int GROUP_MODE_UNGROUPED = 2; |
| |
| private final UiSelector clearAllSelector = |
| new UiSelector().className(Button.class).descriptionContains("CLEAR ALL"); |
| |
| private UiDevice mDevice; |
| private ArrayList<String> mLaunchedPackages; |
| private NotificationManager mNotificationManager; |
| private int mInitialDozeAlwaysOn; |
| |
| public void setUp() throws Exception { |
| mDevice = UiDevice.getInstance(getInstrumentation()); |
| try { |
| mDevice.setOrientationNatural(); |
| } catch (RemoteException e) { |
| throw new RuntimeException("failed to freeze device orientaion", e); |
| } |
| mNotificationManager = getInstrumentation().getContext().getSystemService( |
| NotificationManager.class); |
| InstrumentationRegistry.registerInstance(getInstrumentation(), getArguments()); |
| blockNotifications(); |
| |
| // Enable AOD, otherwise we won't test all animations. Having AOD off also adds |
| // unpredictable fluctuations since the display can take up to 200ms to turn on. |
| AmbientDisplayConfiguration configuration = |
| new AmbientDisplayConfiguration(getInstrumentation().getContext()); |
| mInitialDozeAlwaysOn = configuration.alwaysOnEnabled(UserHandle.USER_SYSTEM) ? 1 : 0; |
| ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver(); |
| Settings.Secure.putInt(contentResolver, Settings.Secure.DOZE_ALWAYS_ON, 1); |
| } |
| |
| public void goHome() { |
| mDevice.pressHome(); |
| mDevice.waitForIdle(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| mDevice.unfreezeRotation(); |
| unblockNotifications(); |
| ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver(); |
| Settings.Secure.putInt(contentResolver, Settings.Secure.DOZE_ALWAYS_ON, |
| mInitialDozeAlwaysOn); |
| super.tearDown(); |
| } |
| |
| public void populateRecentApps() throws IOException { |
| mLaunchedPackages = OverviewHelper.getInstance().populateManyRecentApps(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void forceStopPackages(Bundle metrics) throws IOException { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| OverviewHelper.getInstance().forceStopPackages(mLaunchedPackages); |
| goHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| public static BySelector getLauncherOverviewSelector(UiDevice device) { |
| return By.res(device.getLauncherPackageName(), "overview_panel"); |
| } |
| |
| private BySelector getLauncherOverviewSelector() { |
| return getLauncherOverviewSelector(mDevice); |
| } |
| |
| public static void openRecents(Context context, UiDevice device) { |
| final UiObject2 recentsButton = device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")); |
| if (recentsButton == null) { |
| int height = device.getDisplayHeight(); |
| UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame")); |
| |
| // Swipe from nav bar to 2/3rd down the screen. |
| device.swipe( |
| navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(), |
| navBar.getVisibleBounds().centerX(), height * 2 / 3, |
| (navBar.getVisibleBounds().centerY() - height * 2 / 3) / 100); // 100 px/step |
| } else { |
| recentsButton.click(); |
| } |
| |
| // use a long timeout to wait until recents populated |
| if (device.wait( |
| Until.findObject(isRecentsInLauncher() |
| ? getLauncherOverviewSelector(device) : RECENTS), |
| 10000) == null) { |
| fail("Recents didn't appear"); |
| } |
| device.waitForIdle(); |
| } |
| |
| public void resetRecentsToBottom() throws RemoteException { |
| mDevice.wakeUp(); |
| // Rather than trying to scroll back to the bottom, just re-open the recents list |
| mDevice.pressHome(); |
| mDevice.waitForIdle(); |
| openRecents(getInstrumentation().getTargetContext(), mDevice); |
| } |
| |
| public void prepareNotifications(int groupMode) throws Exception { |
| goHome(); |
| mDevice.openNotification(); |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| |
| // CLEAR ALL might not be visible in case we don't have any clearable notifications. |
| UiObject clearAll = mDevice.findObject(clearAllSelector); |
| if (clearAll.exists()) { |
| clearAll.click(); |
| } |
| mDevice.pressHome(); |
| mDevice.waitForIdle(); |
| postNotifications(groupMode); |
| mDevice.waitForIdle(); |
| } |
| |
| private void postNotifications(int groupMode) { |
| postNotifications(groupMode, 100, -1); |
| } |
| |
| private void postNotifications(int groupMode, int sleepBetweenDuration, int maxCount) { |
| Context context = getInstrumentation().getContext(); |
| Builder builder = new Builder(context) |
| .setContentTitle(NOTIFICATION_TEXT); |
| if (groupMode == GROUP_MODE_GROUPED) { |
| builder.setGroup("key"); |
| } |
| boolean first = true; |
| for (int i = 0; i < ICONS.length; i++) { |
| if (maxCount != -1 && i >= maxCount) { |
| break; |
| } |
| int icon = ICONS[i]; |
| if (first && groupMode == GROUP_MODE_GROUPED) { |
| builder.setGroupSummary(true); |
| } else { |
| builder.setGroupSummary(false); |
| } |
| if (groupMode == GROUP_MODE_UNGROUPED) { |
| builder.setGroup(Integer.toString(icon)); |
| } |
| builder.setContentText(Integer.toHexString(icon)) |
| .setSmallIcon(icon); |
| builder.setContentIntent(PendingIntent.getActivity( |
| context, 0, new Intent(context, DummyActivity.class), 0)); |
| mNotificationManager.notify(icon, builder.build()); |
| SystemClock.sleep(sleepBetweenDuration); |
| first = false; |
| } |
| } |
| |
| private void postInlineReplyNotification() { |
| RemoteInput remoteInput = new RemoteInput.Builder("reply") |
| .setLabel(NOTIFICATION_TEXT) |
| .build(); |
| Context context = getInstrumentation().getTargetContext(); |
| PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0 , new Intent(), |
| PendingIntent.FLAG_UPDATE_CURRENT); |
| Icon icon = Icon.createWithResource(context, ICONS[0]); |
| Action action = new Action.Builder(icon, REPLY_TEXT, pendingIntent) |
| .addRemoteInput(remoteInput) |
| .build(); |
| Builder builder = new Builder(getInstrumentation().getTargetContext()) |
| .setContentTitle(NOTIFICATION_TEXT) |
| .setContentText(NOTIFICATION_TEXT) |
| .setSmallIcon(ICONS[0]) |
| .addAction(action); |
| mNotificationManager.notify(0, builder.build()); |
| } |
| |
| private void cancelNotifications(int sleepBetweenDuration) { |
| for (int icon : ICONS) { |
| mNotificationManager.cancel(icon); |
| SystemClock.sleep(sleepBetweenDuration); |
| } |
| } |
| |
| public void blockNotifications() throws Exception { |
| mDevice.executeShellCommand(DISABLE_COMMAND + GMAIL_PACKAGE_NAME); |
| } |
| |
| public void unblockNotifications() throws Exception { |
| mDevice.executeShellCommand(ENABLE_COMMAND + GMAIL_PACKAGE_NAME); |
| } |
| |
| public void cancelNotifications() throws Exception { |
| mNotificationManager.cancelAll(); |
| } |
| |
| /** |
| * Returns the package that provides Recents. |
| */ |
| public String getPackageForRecents() { |
| return isRecentsInLauncher() ? mDevice.getLauncherPackageName() : SYSTEMUI_PACKAGE; |
| } |
| |
| /** Starts from the bottom of the recent apps list and measures jank while flinging up. */ |
| @JankTest(beforeTest = "populateRecentApps", beforeLoop = "resetRecentsToBottom", |
| afterTest = "forceStopPackages", expectedFrames = 100, defaultIterationCount = 5) |
| @GfxMonitor(processName = "#getPackageForRecents") |
| public void testRecentAppsFling() { |
| final UiObject2 recents; |
| final Direction firstFling, secondFling; |
| |
| if (isRecentsInLauncher()) { |
| recents = mDevice.findObject(getLauncherOverviewSelector()); |
| firstFling = Direction.RIGHT; |
| secondFling = Direction.LEFT; |
| } else { |
| recents = mDevice.findObject(RECENTS); |
| final Rect r = recents.getVisibleBounds(); |
| final int margin = r.height() / 4; // top & bottom edges for fling gesture = 25% height |
| recents.setGestureMargins(0, margin, 0, margin); |
| firstFling = Direction.UP; |
| secondFling = Direction.DOWN; |
| } |
| |
| for (int i = 0; i < INNER_LOOP; i++) { |
| recents.fling(firstFling, DEFAULT_FLING_SPEED); |
| mDevice.waitForIdle(); |
| recents.fling(secondFling, DEFAULT_FLING_SPEED); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| /** |
| * Measures jank when dismissing a task in recents. |
| */ |
| @JankTest(beforeTest = "populateRecentApps", beforeLoop = "resetRecentsToBottom", |
| afterTest = "forceStopPackages", expectedFrames = 10, defaultIterationCount = 5) |
| @GfxMonitor(processName = "#getPackageForRecents") |
| public void testRecentAppsDismiss() { |
| if (isRecentsInLauncher()) { |
| final UiObject2 overviewPanel = mDevice.findObject(getLauncherOverviewSelector()); |
| // Bring some task onto the screen. |
| overviewPanel.fling(Direction.RIGHT, DEFAULT_FLING_SPEED); |
| mDevice.waitForIdle(); |
| |
| for (int i = 0; i < INNER_LOOP; i++) { |
| final List<UiObject2> taskViews = mDevice.findObjects( |
| By.res(mDevice.getLauncherPackageName(), "snapshot")); |
| |
| if (taskViews.size() == 0) { |
| fail("Unable to find a task to dismiss"); |
| } |
| |
| // taskViews contains up to 3 task views: the 'main' (having the widest visible |
| // part) one in the center, and parts of its right and left siblings. Find the |
| // main task view by its width. |
| final UiObject2 widestTask = Collections.max(taskViews, |
| (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(), |
| t2.getVisibleBounds().width())); |
| |
| // Dismiss the task via flinging it up. |
| widestTask.fling(Direction.DOWN); |
| mDevice.waitForIdle(); |
| } |
| |
| } else { |
| // Wait until dismiss views are fully faded in. |
| mDevice.findObject(new UiSelector().resourceId("com.android.systemui:id/dismiss_task")) |
| .waitForExists(5000); |
| for (int i = 0; i < INNER_LOOP; i++) { |
| List<UiObject2> dismissViews = mDevice.findObjects( |
| By.res(SYSTEMUI_PACKAGE, "dismiss_task")); |
| if (dismissViews.size() == 0) { |
| fail("Unable to find dismiss view"); |
| } |
| dismissViews.get(dismissViews.size() - 1).click(); |
| mDevice.waitForIdle(); |
| SystemClock.sleep(500); |
| } |
| } |
| } |
| |
| private void scrollListUp() { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, |
| mDevice.getDisplayHeight() / 2, mDevice.getDisplayWidth() / 2, |
| 0, |
| DEFAULT_SCROLL_STEPS); |
| } |
| |
| private void scrollListDown() { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, |
| mDevice.getDisplayHeight() / 2, mDevice.getDisplayWidth() / 2, |
| mDevice.getDisplayHeight(), |
| DEFAULT_SCROLL_STEPS); |
| } |
| |
| private void swipeDown() { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, |
| SWIPE_MARGIN, mDevice.getDisplayWidth() / 2, |
| mDevice.getDisplayHeight() - SWIPE_MARGIN, |
| DEFAULT_SCROLL_STEPS); |
| } |
| |
| private void swipeUp() { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, |
| mDevice.getDisplayHeight() - SWIPE_MARGIN, |
| mDevice.getDisplayWidth() / 2, |
| SWIPE_MARGIN, |
| DEFAULT_SCROLL_STEPS); |
| } |
| |
| public void beforeNotificationListPull() throws Exception { |
| mDevice.wakeUp(); |
| mDevice.waitForIdle(); |
| prepareNotifications(GROUP_MODE_LEGACY); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterNotificationListPull(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** Measures jank while pulling down the notification list */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeNotificationListPull", afterTest = "afterNotificationListPull") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testNotificationListPull() { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| swipeDown(); |
| mDevice.waitForIdle(); |
| swipeUp(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeNotificationListScroll() throws Exception { |
| prepareNotifications(GROUP_MODE_UNGROUPED); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| swipeDown(); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterNotificationListScroll(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| mDevice.waitForIdle(); |
| swipeUp(); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** Measures jank while scrolling notification list */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeNotificationListScroll", afterTest = "afterNotificationListScroll") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testNotificationListScroll() { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| scrollListUp(); |
| mDevice.waitForIdle(); |
| scrollListDown(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeNotificationListPull_manyNotifications() throws Exception { |
| prepareNotifications(GROUP_MODE_UNGROUPED); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| /** Measures jank while pulling down the notification list with many notifications */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeNotificationListPull_manyNotifications", |
| afterTest = "afterNotificationListPull") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testNotificationListPull_manyNotifications() { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| swipeDown(); |
| mDevice.waitForIdle(); |
| swipeUp(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeQuickSettings() throws Exception { |
| |
| // Make sure we have some notifications. |
| prepareNotifications(GROUP_MODE_UNGROUPED); |
| mDevice.openNotification(); |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterQuickSettings(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** Measures jank while pulling down the quick settings */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeQuickSettings", afterTest = "afterQuickSettings") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testQuickSettingsPull() throws Exception { |
| UiObject quickSettingsButton = mDevice.findObject( |
| new UiSelector().className(ImageView.class) |
| .descriptionContains("quick settings")); |
| for (int i = 0; i < INNER_LOOP; i++) { |
| swipeDown(); |
| mDevice.waitForIdle(); |
| mDevice.pressBack(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeUnlock() throws Exception { |
| |
| // Make sure we have some notifications. |
| prepareNotifications(GROUP_MODE_UNGROUPED); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterUnlock(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measure jank while unlocking the phone. |
| */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeUnlock", afterTest = "afterUnlock") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testUnlock() throws Exception { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| mDevice.sleep(); |
| // Make sure we don't trigger the camera launch double-tap shortcut |
| SystemClock.sleep(300); |
| mDevice.wakeUp(); |
| swipeUp(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeExpand() throws Exception { |
| prepareNotifications(GROUP_MODE_GROUPED); |
| mDevice.openNotification(); |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterExpand(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank while expending a group notification. |
| */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeExpand", afterTest = "afterExpand") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testExpandGroup() throws Exception { |
| UiObject expandButton = mDevice.findObject( |
| new UiSelector().resourceId("android:id/expand_button")); |
| for (int i = 0; i < INNER_LOOP; i++) { |
| expandButton.click(); |
| mDevice.waitForIdle(); |
| expandButton.click(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| private void scrollDown() { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, |
| mDevice.getDisplayHeight() / 2, |
| mDevice.getDisplayWidth() / 2, |
| SWIPE_MARGIN, |
| DEFAULT_SCROLL_STEPS); |
| } |
| |
| public void beforeClearAll() throws Exception { |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void beforeClearAllLoop() throws Exception { |
| postNotifications(GROUP_MODE_UNGROUPED); |
| mDevice.openNotification(); |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterClearAll(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when clicking the "clear all" button in the notification shade. |
| */ |
| @JankTest(expectedFrames = 10, |
| defaultIterationCount = 5, |
| beforeTest = "beforeClearAll", |
| beforeLoop = "beforeClearAllLoop", |
| afterTest = "afterClearAll") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testClearAll() throws Exception { |
| UiObject clearAll = mDevice.findObject(clearAllSelector); |
| while (!clearAll.exists()) { |
| scrollDown(); |
| } |
| clearAll.click(); |
| mDevice.waitForIdle(); |
| } |
| |
| public void beforeChangeBrightness() throws Exception { |
| mDevice.openQuickSettings(); |
| |
| // Wait until animation is starting. |
| SystemClock.sleep(200); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterChangeBrightness(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when changing screen brightness |
| */ |
| @JankTest(expectedFrames = 10, |
| defaultIterationCount = 5, |
| beforeTest = "beforeChangeBrightness", |
| afterTest = "afterChangeBrightness") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testChangeBrightness() throws Exception { |
| UiObject2 brightness = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, "slider")); |
| Rect bounds = brightness.getVisibleBounds(); |
| for (int i = 0; i < INNER_LOOP; i++) { |
| mDevice.swipe(bounds.left, bounds.centerY(), |
| bounds.right, bounds.centerY(), BRIGHTNESS_SCROLL_STEPS); |
| |
| // Make sure animation is completing. |
| SystemClock.sleep(500); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeNotificationAppear() throws Exception { |
| mDevice.openNotification(); |
| |
| // Wait until animation is starting. |
| SystemClock.sleep(200); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterNotificationAppear(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when a notification is appearing. |
| */ |
| @JankTest(expectedFrames = 10, |
| defaultIterationCount = 5, |
| beforeTest = "beforeNotificationAppear", |
| afterTest = "afterNotificationAppear") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testNotificationAppear() throws Exception { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| postNotifications(GROUP_MODE_UNGROUPED, 250, 5); |
| mDevice.waitForIdle(); |
| cancelNotifications(250); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeCameraFromLockscreen() throws Exception { |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void beforeCameraFromLockscreenLoop() throws Exception { |
| mDevice.pressHome(); |
| mDevice.sleep(); |
| // Make sure we don't trigger the camera launch double-tap shortcut |
| SystemClock.sleep(300); |
| mDevice.wakeUp(); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterCameraFromLockscreen(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when launching the camera from lockscreen. |
| */ |
| @JankTest(expectedFrames = 10, |
| defaultIterationCount = 5, |
| beforeTest = "beforeCameraFromLockscreen", |
| afterTest = "afterCameraFromLockscreen", |
| beforeLoop = "beforeCameraFromLockscreenLoop") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testCameraFromLockscreen() throws Exception { |
| mDevice.swipe(mDevice.getDisplayWidth() - SWIPE_MARGIN, |
| mDevice.getDisplayHeight() - SWIPE_MARGIN, SWIPE_MARGIN, SWIPE_MARGIN, |
| DEFAULT_SCROLL_STEPS); |
| mDevice.waitForIdle(); |
| } |
| |
| public void beforeAmbientWakeUp() throws Exception { |
| postNotifications(GROUP_MODE_UNGROUPED); |
| mDevice.sleep(); |
| SystemClock.sleep(1000); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterAmbientWakeUp(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| mDevice.wakeUp(); |
| mDevice.waitForIdle(); |
| mDevice.pressMenu(); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when waking up from ambient (doze) display. |
| */ |
| @JankTest(expectedFrames = 30, |
| defaultIterationCount = 5, |
| beforeTest = "beforeAmbientWakeUp", |
| afterTest = "afterAmbientWakeUp") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testAmbientWakeUp() throws Exception { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| mDevice.executeShellCommand(PULSE_COMMAND); |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| mDevice.wakeUp(); |
| SystemClock.sleep(500); |
| mDevice.waitForIdle(); |
| mDevice.sleep(); |
| SystemClock.sleep(1000); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeGoToFullShade() throws Exception { |
| postNotifications(GROUP_MODE_UNGROUPED); |
| mDevice.sleep(); |
| |
| // Don't trigger camera launch gesture |
| SystemClock.sleep(300); |
| mDevice.wakeUp(); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterGoToFullShade(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| mDevice.pressMenu(); |
| mDevice.waitForIdle(); |
| cancelNotifications(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when tragging down on a notification on the lockscreen to go to the full shade. |
| */ |
| @JankTest(expectedFrames = 100, |
| defaultIterationCount = 5, |
| beforeTest = "beforeGoToFullShade", |
| afterTest = "afterGoToFullShade") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testGoToFullShade() throws Exception { |
| for (int i = 0; i < INNER_LOOP; i++) { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2, |
| mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() - SWIPE_MARGIN, |
| DEFAULT_SCROLL_STEPS); |
| mDevice.waitForIdle(); |
| mDevice.click(mDevice.getDisplayWidth() / 4, mDevice.getDisplayHeight() - SWIPE_MARGIN); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforeInlineReply() throws Exception { |
| postInlineReplyNotification(); |
| mDevice.openNotification(); |
| |
| // Wait until animation kicks in |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterInlineReply(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| goHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when clicking "reply" on a notification that supports inline reply. |
| */ |
| @JankTest(expectedFrames = 50, |
| defaultIterationCount = 5, |
| beforeTest = "beforeInlineReply", |
| afterTest = "afterInlineReply") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testInlineReply() throws Exception { |
| |
| UiSelector replySelector = new UiSelector().className(ImageView.class) |
| .descriptionContains(REPLY_TEXT); |
| UiObject replyButton = mDevice.findObject(replySelector); |
| |
| assertNotNull("Could not find button with text '" + REPLY_TEXT + "'.", replyButton); |
| for (int i = 0; i < INNER_LOOP; i++) { |
| replyButton.click(); |
| mDevice.waitForIdle(); |
| Thread.sleep(1000); |
| mDevice.pressBack(); |
| mDevice.waitForIdle(); |
| } |
| } |
| |
| public void beforePinAppearance() throws Exception { |
| LockscreenHelper.getInstance().setScreenLockViaShell(PIN, LockscreenHelper.MODE_PIN); |
| goHome(); |
| mDevice.sleep(); |
| SystemClock.sleep(300); |
| mDevice.wakeUp(); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void afterPinAppearanceLoop() throws Exception { |
| mDevice.pressBack(); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterPinAppearance(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| LockscreenHelper.getInstance().unlockScreen(PIN); |
| LockscreenHelper.getInstance().removeScreenLockViaShell(PIN); |
| mDevice.pressHome(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when swiping up and showing pin on lockscreen. |
| */ |
| @JankTest(expectedFrames = 20, |
| defaultIterationCount = 5, |
| beforeTest = "beforePinAppearance", |
| afterTest = "afterPinAppearance", |
| afterLoop = "afterPinAppearanceLoop") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testPinAppearance() throws Exception { |
| mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() - SWIPE_MARGIN, |
| mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2, |
| DEFAULT_SCROLL_STEPS); |
| mDevice.waitForIdle(); |
| String command = String.format("%s %s %s", "input", "text", PIN); |
| mDevice.executeShellCommand(command); |
| mDevice.waitForIdle(); |
| } |
| |
| public void beforeLaunchSettings() throws Exception { |
| prepareNotifications(GROUP_MODE_UNGROUPED); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void beforeLaunchSettingsLoop() throws Exception { |
| mDevice.openQuickSettings(); |
| |
| // Wait until animation kicks in |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterLaunchSettingsLoop() throws Exception { |
| mDevice.executeShellCommand("am force-stop " + SETTINGS_PACKAGE); |
| mDevice.pressHome(); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterLaunchSettings(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when launching settings from notification shade |
| */ |
| @JankTest(expectedFrames = 30, |
| defaultIterationCount = 5, |
| beforeTest = "beforeLaunchSettings", |
| afterTest = "afterLaunchSettings", |
| beforeLoop = "beforeLaunchSettingsLoop", |
| afterLoop = "afterLaunchSettingsLoop") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testLaunchSettings() throws Exception { |
| mDevice.findObject(By.res(SYSTEMUI_PACKAGE, "settings_button")).click(); |
| // Wait until animation kicks in |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| } |
| |
| public void beforeLaunchNotification() throws Exception { |
| prepareNotifications(GROUP_MODE_UNGROUPED); |
| TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| } |
| |
| public void beforeLaunchNotificationLoop() throws Exception { |
| mDevice.openNotification(); |
| |
| // Wait until animation kicks in |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterLaunchNotificationLoop() throws Exception { |
| mDevice.pressHome(); |
| mDevice.waitForIdle(); |
| } |
| |
| public void afterLaunchNotification(Bundle metrics) throws Exception { |
| TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), TIMESTAMP_FILE); |
| cancelNotifications(); |
| TimeResultLogger.writeResultToFile(String.format("%s-%s", |
| getClass().getSimpleName(), getName()), RESULTS_FILE, metrics); |
| super.afterTest(metrics); |
| } |
| |
| /** |
| * Measures jank when launching a notification |
| */ |
| @JankTest(expectedFrames = 30, |
| defaultIterationCount = 5, |
| beforeTest = "beforeLaunchNotification", |
| afterTest = "afterLaunchNotification", |
| beforeLoop = "beforeLaunchNotificationLoop", |
| afterLoop = "afterLaunchNotificationLoop") |
| @GfxMonitor(processName = SYSTEMUI_PACKAGE) |
| public void testLaunchNotification() throws Exception { |
| mDevice.findObject(By.text("Lorem ipsum dolor sit amet")).click(); |
| |
| // Wait until animation kicks in |
| SystemClock.sleep(100); |
| mDevice.waitForIdle(); |
| } |
| } |