InstrumentationUtils.java revision 1dbff05bf375557ea73e07632d732bd90a71af9e
1367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin/* 2367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Copyright (C) 2015 DroidDriver committers 3367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * 4367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Licensed under the Apache License, Version 2.0 (the "License"); 5367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * you may not use this file except in compliance with the License. 6367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * You may obtain a copy of the License at 7367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * 8367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * http://www.apache.org/licenses/LICENSE-2.0 9367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * 10367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Unless required by applicable law or agreed to in writing, software 11367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * distributed under the License is distributed on an "AS IS" BASIS, 12367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * See the License for the specific language governing permissions and 14367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * limitations under the License. 15367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 16367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 17367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinpackage io.appium.droiddriver.util; 18367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 19367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.app.Instrumentation; 20367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.os.Bundle; 21367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.os.Looper; 22367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.util.Log; 23367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 24367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport java.util.concurrent.Callable; 251dbff05bf375557ea73e07632d732bd90a71af9eKevin Jinimport java.util.concurrent.Executor; 261dbff05bf375557ea73e07632d732bd90a71af9eKevin Jinimport java.util.concurrent.Executors; 27367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport java.util.concurrent.FutureTask; 28367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport java.util.concurrent.TimeUnit; 29367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 30367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport io.appium.droiddriver.exceptions.DroidDriverException; 31367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport io.appium.droiddriver.exceptions.TimeoutException; 32367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport io.appium.droiddriver.exceptions.UnrecoverableException; 33367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 34367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin/** 35367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Static utility methods pertaining to {@link Instrumentation}. 36367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 37367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinpublic class InstrumentationUtils { 38367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static Instrumentation instrumentation; 39367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static Bundle options; 40367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static long runOnMainSyncTimeoutMillis; 41367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static final Runnable EMPTY_RUNNABLE = new Runnable() { 42367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin @Override 43367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public void run() { 44367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 45367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin }; 461dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin private static final Executor RUN_ON_MAIN_SYNC_EXECUTOR = Executors.newSingleThreadExecutor(); 47367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 48367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 49367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Initializes this class. If you use a runner that is not DroidDriver-aware, you need to call 50367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * this method appropriately. See {@link io.appium.droiddriver.runner.TestRunner#onCreate} for 51367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * example. 52367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 53367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static void init(Instrumentation instrumentation, Bundle arguments) { 54367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (InstrumentationUtils.instrumentation != null) { 55367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new DroidDriverException("init() can only be called once"); 56367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 57367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin InstrumentationUtils.instrumentation = instrumentation; 58367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin options = arguments; 59367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 60367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin String timeoutString = getD2Option("runOnMainSyncTimeout"); 61367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin runOnMainSyncTimeoutMillis = timeoutString == null ? 10000L : Long.parseLong(timeoutString); 62367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 63367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 64367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static void checkInitialized() { 65367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (instrumentation == null) { 66367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new UnrecoverableException("If you use a runner that is not DroidDriver-aware, you" + 67367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin " need to call InstrumentationUtils.init appropriately"); 68367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 69367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 70367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 71367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static Instrumentation getInstrumentation() { 72367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin checkInitialized(); 73367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return instrumentation; 74367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 75367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 76367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 77367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Gets the <a href= "http://developer.android.com/tools/testing/testing_otheride.html#AMOptionsSyntax" 78367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * >am instrument options</a>. 79367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 80367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static Bundle getOptions() { 81367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin checkInitialized(); 82367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return options; 83367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 84367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 85367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 86367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Gets the string value associated with the given key. This is preferred over using {@link 87367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * #getOptions} because the returned {@link Bundle} contains only string values - am instrument 88367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * options do not support value types other than string. 89367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 90367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static String getOption(String key) { 91367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return getOptions().getString(key); 92367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 93367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 94367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 95367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Calls {@link #getOption} with "dd." prefixed to {@code key}. This is for DroidDriver 96367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * implementation to use a consistent pattern for its options. 97367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 98367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static String getD2Option(String key) { 99367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return getOption("dd." + key); 100367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 101367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 102367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 103367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Tries to wait for an idle state on the main thread on best-effort basis up to {@code 104367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * timeoutMillis}. The main thread may not enter the idle state when animation is playing, for 105367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * example, the ProgressBar. 106367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 107367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static boolean tryWaitForIdleSync(long timeoutMillis) { 108367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin validateNotAppThread(); 109367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin FutureTask<Void> emptyTask = new FutureTask<Void>(EMPTY_RUNNABLE, null); 110367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin instrumentation.waitForIdle(emptyTask); 111367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 112367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin try { 113367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin emptyTask.get(timeoutMillis, TimeUnit.MILLISECONDS); 114367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } catch (java.util.concurrent.TimeoutException e) { 115367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin Logs.log(Log.INFO, 116367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin "Timed out after " + timeoutMillis + " milliseconds waiting for idle on main looper"); 117367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return false; 1181dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin } catch (Throwable t) { 1191dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin throw DroidDriverException.propagate(t); 120367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 121367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return true; 122367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 123367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 124367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static void runOnMainSyncWithTimeout(final Runnable runnable) { 125367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin runOnMainSyncWithTimeout(new Callable<Void>() { 126367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin @Override 127367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public Void call() throws Exception { 128367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin runnable.run(); 129367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return null; 130367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 131367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin }); 132367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 133367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 134367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 135367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Runs {@code callable} on the main thread on best-effort basis up to a time limit, which 136367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * defaults to {@code 10000L} and can be set as an am instrument option under the key {@code 137367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * dd.runOnMainSyncTimeout}. <p>This is a safer variation of {@link Instrumentation#runOnMainSync} 1381dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin * because the latter may hang. You may turn off this behavior by setting {@code "-e 1391dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin * dd.runOnMainSyncTimeout 0"} on the am command line.</p>The {@code callable} may never run, for 1401dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin * example, if the main Looper has exited due to uncaught exception. 141367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 142367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static <V> V runOnMainSyncWithTimeout(Callable<V> callable) { 143367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin validateNotAppThread(); 144367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin final RunOnMainSyncFutureTask<V> futureTask = new RunOnMainSyncFutureTask<>(callable); 145367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 146367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (runOnMainSyncTimeoutMillis <= 0L) { 147367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin // Call runOnMainSync on current thread without time limit. 148367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin futureTask.runOnMainSyncNoThrow(); 149367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } else { 1501dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin RUN_ON_MAIN_SYNC_EXECUTOR.execute(new Runnable() { 151367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin @Override 152367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public void run() { 153367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin futureTask.runOnMainSyncNoThrow(); 154367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 1551dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin }); 156367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 157367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 158367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin try { 159367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return futureTask.get(runOnMainSyncTimeoutMillis, TimeUnit.MILLISECONDS); 160367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } catch (java.util.concurrent.TimeoutException e) { 161367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new TimeoutException("Timed out after " + runOnMainSyncTimeoutMillis 162367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin + " milliseconds waiting for Instrumentation.runOnMainSync", e); 1631dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin } catch (Throwable t) { 1641dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin throw DroidDriverException.propagate(t); 165367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } finally { 166367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin futureTask.cancel(false); 167367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 168367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 169367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 170367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static class RunOnMainSyncFutureTask<V> extends FutureTask<V> { 171367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public RunOnMainSyncFutureTask(Callable<V> callable) { 172367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin super(callable); 173367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 174367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 175367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public void runOnMainSyncNoThrow() { 176367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin try { 177367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin getInstrumentation().runOnMainSync(this); 178367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } catch (Throwable e) { 179367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin setException(e); 180367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 181367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 182367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 183367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 184367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static void validateNotAppThread() { 185367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (Looper.myLooper() == Looper.getMainLooper()) { 186367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new DroidDriverException( 187367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin "This method can not be called from the main application thread"); 188367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 189367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 190367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin} 191