1 /*
2  * Copyright (C) 2015 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.platform.systemui.tests.jank;
18 
19 import android.app.Notification.Builder;
20 import android.app.NotificationManager;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.graphics.Rect;
26 import android.os.Bundle;
27 import android.os.RemoteException;
28 import android.os.SystemClock;
29 import android.support.test.jank.GfxMonitor;
30 import android.support.test.jank.JankTest;
31 import android.support.test.jank.JankTestBase;
32 import android.support.test.uiautomator.By;
33 import android.support.test.uiautomator.BySelector;
34 import android.support.test.uiautomator.UiDevice;
35 import android.support.test.uiautomator.UiObject2;
36 import android.support.test.uiautomator.Until;
37 import android.support.test.timeresulthelper.TimeResultLogger;
38 import android.util.Log;
39 import android.os.Environment;
40 
41 import java.io.File;
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.List;
45 
46 public class SystemUiJankTests extends JankTestBase {
47 
48     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
49     private static final BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view");
50     private static final String LOG_TAG = SystemUiJankTests.class.getSimpleName();
51     private static final int SWIPE_MARGIN = 5;
52     private static final int DEFAULT_FLING_STEPS = 5;
53     private static final int DEFAULT_SCROLL_STEPS = 15;
54     // short transitions should be repeated within the test function, otherwise frame stats
55     // captured are not really meaningful in a statistical sense
56     private static final int INNER_LOOP = 3;
57     private static final int[] ICONS = new int[] {
58             android.R.drawable.stat_notify_call_mute,
59             android.R.drawable.stat_notify_chat,
60             android.R.drawable.stat_notify_error,
61             android.R.drawable.stat_notify_missed_call,
62             android.R.drawable.stat_notify_more,
63             android.R.drawable.stat_notify_sdcard,
64             android.R.drawable.stat_notify_sdcard_prepare,
65             android.R.drawable.stat_notify_sdcard_usb,
66             android.R.drawable.stat_notify_sync,
67             android.R.drawable.stat_notify_sync_noanim,
68             android.R.drawable.stat_notify_voicemail,
69     };
70     private static final String NOTIFICATION_TEXT = "Lorem ipsum dolor sit amet";
71     private static final File TIMESTAMP_FILE = new File(Environment.getExternalStorageDirectory()
72             .getAbsolutePath(), "autotester.log");
73     private static final File RESULTS_FILE = new File(Environment.getExternalStorageDirectory()
74             .getAbsolutePath(), "results.log");
75 
76     private UiDevice mDevice;
77     private List<String> mLaunchedPackages = new ArrayList<>();
78 
setUp()79     public void setUp() {
80         mDevice = UiDevice.getInstance(getInstrumentation());
81         try {
82             mDevice.setOrientationNatural();
83         } catch (RemoteException e) {
84             throw new RuntimeException("failed to freeze device orientaion", e);
85         }
86     }
87 
goHome()88     public void goHome() {
89         mDevice.pressHome();
90         mDevice.waitForIdle();
91     }
92 
93     @Override
tearDown()94     protected void tearDown() throws Exception {
95         mDevice.unfreezeRotation();
96         super.tearDown();
97     }
98 
populateRecentApps()99     public void populateRecentApps() throws IOException {
100         PackageManager pm = getInstrumentation().getContext().getPackageManager();
101         List<PackageInfo> packages = pm.getInstalledPackages(0);
102         mLaunchedPackages.clear();
103         for (PackageInfo pkg : packages) {
104             if (pkg.packageName.equals(getInstrumentation().getTargetContext().getPackageName())) {
105                 continue;
106             }
107             Intent intent = pm.getLaunchIntentForPackage(pkg.packageName);
108             if (intent == null) {
109                 continue;
110             }
111             intent.addCategory(Intent.CATEGORY_LAUNCHER);
112             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
113             getInstrumentation().getTargetContext().startActivity(intent);
114             SystemClock.sleep(5000);
115             mLaunchedPackages.add(pkg.packageName);
116         }
117 
118         // Close any crash dialogs
119         while (mDevice.hasObject(By.textContains("has stopped"))) {
120             mDevice.findObject(By.text("Close")).clickAndWait(Until.newWindow(), 2000);
121         }
122         TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
123                 getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
124     }
125 
forceStopPackages(Bundle metrics)126     public void forceStopPackages(Bundle metrics) throws IOException {
127         TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
128                 getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
129         for (String pkg : mLaunchedPackages) {
130             try {
131                 mDevice.executeShellCommand("am force-stop " + pkg);
132             } catch (IOException e) {
133                 Log.w(LOG_TAG, "exeception while force stopping package " + pkg, e);
134             }
135         }
136         goHome();
137         TimeResultLogger.writeResultToFile(String.format("%s-%s",
138                 getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
139         super.afterTest(metrics);
140     }
141 
resetRecentsToBottom()142     public void resetRecentsToBottom() {
143         // Rather than trying to scroll back to the bottom, just re-open the recents list
144         mDevice.pressHome();
145         mDevice.waitForIdle();
146         try {
147             mDevice.pressRecentApps();
148         } catch (RemoteException e) {
149             throw new RuntimeException(e);
150         }
151         // use a long timeout to wait until recents populated
152         mDevice.wait(Until.findObject(RECENTS), 10000);
153         mDevice.waitForIdle();
154     }
155 
prepareNotifications()156     public void prepareNotifications() throws IOException {
157         goHome();
158         Builder builder = new Builder(getInstrumentation().getTargetContext())
159                 .setContentTitle(NOTIFICATION_TEXT);
160         NotificationManager nm = (NotificationManager) getInstrumentation().getTargetContext()
161                 .getSystemService(Context.NOTIFICATION_SERVICE);
162         for (int icon : ICONS) {
163             builder.setContentText(Integer.toHexString(icon))
164                     .setSmallIcon(icon);
165             nm.notify(icon, builder.build());
166             SystemClock.sleep(100);
167         }
168         mDevice.waitForIdle();
169         TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
170                 getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
171     }
172 
cancelNotifications(Bundle metrics)173     public void cancelNotifications(Bundle metrics) throws IOException {
174         TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
175                 getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
176         NotificationManager nm = (NotificationManager) getInstrumentation().getTargetContext()
177                 .getSystemService(Context.NOTIFICATION_SERVICE);
178         nm.cancelAll();
179         TimeResultLogger.writeResultToFile(String.format("%s-%s",
180                 getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
181         super.afterTest(metrics);
182     }
183 
184     /** Starts from the bottom of the recent apps list and measures jank while flinging up. */
185     @JankTest(beforeTest = "populateRecentApps", beforeLoop = "resetRecentsToBottom",
186             afterTest = "forceStopPackages", expectedFrames = 100)
187     @GfxMonitor(processName = SYSTEMUI_PACKAGE)
testRecentAppsFling()188     public void testRecentAppsFling() {
189         UiObject2 recents = mDevice.findObject(RECENTS);
190         Rect r = recents.getVisibleBounds();
191         // decide the top & bottom edges for scroll gesture
192         int top = r.top + r.height() / 4; // top edge = top + 25% height
193         int bottom = r.bottom - 200; // bottom edge = bottom & shift up 200px
194         for (int i = 0; i < INNER_LOOP; i++) {
195             mDevice.swipe(r.width() / 2, top, r.width() / 2, bottom, DEFAULT_FLING_STEPS);
196             mDevice.waitForIdle();
197             mDevice.swipe(r.width() / 2, bottom, r.width() / 2, top, DEFAULT_FLING_STEPS);
198             mDevice.waitForIdle();
199         }
200     }
201 
openNotification()202     private void openNotification() {
203         mDevice.swipe(mDevice.getDisplayWidth() / 2,
204                 SWIPE_MARGIN, mDevice.getDisplayWidth() / 2,
205                 mDevice.getDisplayHeight() - SWIPE_MARGIN,
206                 DEFAULT_SCROLL_STEPS);
207     }
208 
closeNotification()209     private void closeNotification() {
210         mDevice.swipe(mDevice.getDisplayWidth() / 2,
211                 mDevice.getDisplayHeight() - SWIPE_MARGIN,
212                 mDevice.getDisplayWidth() / 2,
213                 SWIPE_MARGIN,
214                 DEFAULT_SCROLL_STEPS);
215     }
216 
217     /** Measures jank while pulling down the notification list */
218     @JankTest(expectedFrames = 100,
219             beforeTest = "prepareNotifications", afterTest = "cancelNotifications")
220     @GfxMonitor(processName = SYSTEMUI_PACKAGE)
testNotificationListPull()221     public void testNotificationListPull() {
222         for (int i = 0; i < INNER_LOOP; i++) {
223             openNotification();
224             mDevice.waitForIdle();
225             closeNotification();
226             mDevice.waitForIdle();
227         }
228     }
229 }
230 
231