1 /** 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 * express or implied. See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 15 package android.accessibilityservice.cts.utils; 16 17 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.fail; 21 22 import android.accessibilityservice.AccessibilityService; 23 import android.accessibilityservice.AccessibilityServiceInfo; 24 import android.app.Activity; 25 import android.app.Instrumentation; 26 import android.app.UiAutomation; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.PackageManager; 30 import android.content.pm.ResolveInfo; 31 import android.graphics.Rect; 32 import android.os.PowerManager; 33 import android.os.SystemClock; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.InputDevice; 37 import android.view.KeyCharacterMap; 38 import android.view.KeyEvent; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.view.accessibility.AccessibilityNodeInfo; 41 import android.view.accessibility.AccessibilityWindowInfo; 42 43 import androidx.test.rule.ActivityTestRule; 44 45 import com.android.compatibility.common.util.TestUtils; 46 47 import java.util.List; 48 import java.util.Objects; 49 import java.util.function.BooleanSupplier; 50 import java.util.stream.Collectors; 51 52 /** 53 * Utilities useful when launching an activity to make sure it's all the way on the screen 54 * before we start testing it. 55 */ 56 public class ActivityLaunchUtils { 57 private static final String LOG_TAG = "ActivityLaunchUtils"; 58 59 // Using a static variable so it can be used in lambdas. Not preserving state in it. 60 private static Activity mTempActivity; 61 launchActivityAndWaitForItToBeOnscreen( Instrumentation instrumentation, UiAutomation uiAutomation, ActivityTestRule<T> rule)62 public static <T extends Activity> T launchActivityAndWaitForItToBeOnscreen( 63 Instrumentation instrumentation, UiAutomation uiAutomation, 64 ActivityTestRule<T> rule) throws Exception { 65 final int[] location = new int[2]; 66 final StringBuilder activityPackage = new StringBuilder(); 67 final Rect bounds = new Rect(); 68 final StringBuilder activityTitle = new StringBuilder(); 69 // Make sure we get window events, so we'll know when the window appears 70 AccessibilityServiceInfo info = uiAutomation.getServiceInfo(); 71 info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 72 uiAutomation.setServiceInfo(info); 73 homeScreenOrBust(instrumentation.getContext(), uiAutomation); 74 final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent( 75 () -> { 76 mTempActivity = rule.launchActivity(null); 77 final StringBuilder builder = new StringBuilder(); 78 instrumentation.runOnMainSync(() -> { 79 mTempActivity.getWindow().getDecorView().getLocationOnScreen(location); 80 activityPackage.append(mTempActivity.getPackageName()); 81 }); 82 instrumentation.waitForIdleSync(); 83 activityTitle.append(getActivityTitle(instrumentation, mTempActivity)); 84 }, 85 (event) -> { 86 final AccessibilityWindowInfo window = 87 findWindowByTitle(uiAutomation, activityTitle); 88 if (window == null) return false; 89 window.getBoundsInScreen(bounds); 90 mTempActivity.getWindow().getDecorView().getLocationOnScreen(location); 91 if (bounds.isEmpty()) { 92 return false; 93 } 94 return (!bounds.isEmpty()) 95 && (bounds.left == location[0]) && (bounds.top == location[1]); 96 }, DEFAULT_TIMEOUT_MS); 97 assertNotNull(awaitedEvent); 98 return (T) mTempActivity; 99 } 100 getActivityTitle( Instrumentation instrumentation, Activity activity)101 public static CharSequence getActivityTitle( 102 Instrumentation instrumentation, Activity activity) { 103 final StringBuilder titleBuilder = new StringBuilder(); 104 instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle())); 105 return titleBuilder; 106 } 107 findWindowByTitle( UiAutomation uiAutomation, CharSequence title)108 public static AccessibilityWindowInfo findWindowByTitle( 109 UiAutomation uiAutomation, CharSequence title) { 110 final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows(); 111 AccessibilityWindowInfo returnValue = null; 112 for (int i = 0; i < windows.size(); i++) { 113 final AccessibilityWindowInfo window = windows.get(i); 114 if (TextUtils.equals(title, window.getTitle())) { 115 returnValue = window; 116 } else { 117 window.recycle(); 118 } 119 } 120 return returnValue; 121 } 122 homeScreenOrBust(Context context, UiAutomation uiAutomation)123 public static void homeScreenOrBust(Context context, UiAutomation uiAutomation) { 124 wakeUpOrBust(context, uiAutomation); 125 if (context.getPackageManager().isInstantApp()) return; 126 if (isHomeScreenShowing(context, uiAutomation)) return; 127 try { 128 executeAndWaitOn( 129 uiAutomation, 130 () -> uiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME), 131 () -> isHomeScreenShowing(context, uiAutomation), 132 DEFAULT_TIMEOUT_MS, 133 "home screen"); 134 } catch (AssertionError error) { 135 Log.e(LOG_TAG, "Timed out looking for home screen. Dumping window list"); 136 final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows(); 137 if (windows == null) { 138 Log.e(LOG_TAG, "Window list is null"); 139 } else if (windows.isEmpty()) { 140 Log.e(LOG_TAG, "Window list is empty"); 141 } else { 142 for (AccessibilityWindowInfo window : windows) { 143 Log.e(LOG_TAG, window.toString()); 144 } 145 } 146 147 fail("Unable to reach home screen"); 148 } 149 } 150 isHomeScreenShowing(Context context, UiAutomation uiAutomation)151 private static boolean isHomeScreenShowing(Context context, UiAutomation uiAutomation) { 152 final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows(); 153 final PackageManager packageManager = context.getPackageManager(); 154 final List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities( 155 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), 156 PackageManager.MATCH_DEFAULT_ONLY); 157 158 // Look for a window with a package name that matches the default home screen 159 for (AccessibilityWindowInfo window : windows) { 160 final AccessibilityNodeInfo root = window.getRoot(); 161 if (root != null) { 162 final CharSequence packageName = root.getPackageName(); 163 if (packageName != null) { 164 for (ResolveInfo resolveInfo : resolveInfos) { 165 if ((resolveInfo.activityInfo != null) 166 && packageName.equals(resolveInfo.activityInfo.packageName)) { 167 return true; 168 } 169 } 170 } 171 } 172 } 173 // List unexpected package names of default home screen that invoking ResolverActivity 174 final CharSequence homePackageNames = resolveInfos.stream() 175 .map(r -> r.activityInfo).filter(Objects::nonNull) 176 .map(a -> a.packageName).collect(Collectors.joining(", ")); 177 Log.v(LOG_TAG, "No window matched with package names of home screen: " + homePackageNames); 178 return false; 179 } 180 wakeUpOrBust(Context context, UiAutomation uiAutomation)181 private static void wakeUpOrBust(Context context, UiAutomation uiAutomation) { 182 final long deadlineUptimeMillis = SystemClock.uptimeMillis() + DEFAULT_TIMEOUT_MS; 183 final PowerManager powerManager = context.getSystemService(PowerManager.class); 184 do { 185 if (powerManager.isInteractive()) { 186 Log.d(LOG_TAG, "Device is interactive"); 187 return; 188 } 189 190 Log.d(LOG_TAG, "Sending wakeup keycode"); 191 final long eventTime = SystemClock.uptimeMillis(); 192 uiAutomation.injectInputEvent( 193 new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, 194 KeyEvent.KEYCODE_WAKEUP, 0 /* repeat */, 0 /* metastate */, 195 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 0 /* flags */, 196 InputDevice.SOURCE_KEYBOARD), true /* sync */); 197 uiAutomation.injectInputEvent( 198 new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, 199 KeyEvent.KEYCODE_WAKEUP, 0 /* repeat */, 0 /* metastate */, 200 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 0 /* flags */, 201 InputDevice.SOURCE_KEYBOARD), true /* sync */); 202 try { 203 Thread.sleep(50); 204 } catch (InterruptedException e) {} 205 } while (SystemClock.uptimeMillis() < deadlineUptimeMillis); 206 fail("Unable to wake up screen"); 207 } 208 209 /** 210 * Executes a command and waits for a specified condition up to a given wait timeout. It checks 211 * condition result each time when events delivered, and throws exception if the condition 212 * result is not {@code true} within the given timeout. 213 */ executeAndWaitOn(UiAutomation uiAutomation, Runnable command, BooleanSupplier condition, long timeoutMillis, String conditionName)214 private static void executeAndWaitOn(UiAutomation uiAutomation, Runnable command, 215 BooleanSupplier condition, long timeoutMillis, String conditionName) { 216 final Object waitObject = new Object(); 217 final long executionStartTimeMillis = SystemClock.uptimeMillis(); 218 try { 219 uiAutomation.setOnAccessibilityEventListener((event) -> { 220 if (event.getEventTime() < executionStartTimeMillis) { 221 return; 222 } 223 synchronized (waitObject) { 224 waitObject.notifyAll(); 225 } 226 }); 227 command.run(); 228 TestUtils.waitOn(waitObject, condition, timeoutMillis, conditionName); 229 } finally { 230 uiAutomation.setOnAccessibilityEventListener(null); 231 } 232 } 233 } 234