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; 204c349e0101d6b7a1057722aeb21d7955b0eb0aebKevin Jinimport android.content.Context; 21367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.os.Bundle; 22367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.os.Looper; 23367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport android.util.Log; 24367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 25367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport java.util.concurrent.Callable; 261dbff05bf375557ea73e07632d732bd90a71af9eKevin Jinimport java.util.concurrent.Executor; 271dbff05bf375557ea73e07632d732bd90a71af9eKevin Jinimport java.util.concurrent.Executors; 28367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport java.util.concurrent.FutureTask; 29367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport java.util.concurrent.TimeUnit; 30367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 31367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport io.appium.droiddriver.exceptions.DroidDriverException; 32367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport io.appium.droiddriver.exceptions.TimeoutException; 33367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinimport io.appium.droiddriver.exceptions.UnrecoverableException; 34367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 35367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin/** 36367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Static utility methods pertaining to {@link Instrumentation}. 37367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 38367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jinpublic class InstrumentationUtils { 39367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static Instrumentation instrumentation; 40367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static Bundle options; 41367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static long runOnMainSyncTimeoutMillis; 42367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static final Runnable EMPTY_RUNNABLE = new Runnable() { 43367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin @Override 44367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public void run() { 45367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 46367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin }; 471dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin private static final Executor RUN_ON_MAIN_SYNC_EXECUTOR = Executors.newSingleThreadExecutor(); 48367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 49367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 50367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Initializes this class. If you use a runner that is not DroidDriver-aware, you need to call 51367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * this method appropriately. See {@link io.appium.droiddriver.runner.TestRunner#onCreate} for 52367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * example. 53367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 54367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static void init(Instrumentation instrumentation, Bundle arguments) { 55367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (InstrumentationUtils.instrumentation != null) { 56367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new DroidDriverException("init() can only be called once"); 57367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 58367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin InstrumentationUtils.instrumentation = instrumentation; 59367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin options = arguments; 60367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 61367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin String timeoutString = getD2Option("runOnMainSyncTimeout"); 62367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin runOnMainSyncTimeoutMillis = timeoutString == null ? 10000L : Long.parseLong(timeoutString); 63367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 64367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 65367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static void checkInitialized() { 66367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (instrumentation == null) { 67367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new UnrecoverableException("If you use a runner that is not DroidDriver-aware, you" + 68367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin " need to call InstrumentationUtils.init appropriately"); 69367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 70367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 71367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 72367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static Instrumentation getInstrumentation() { 73367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin checkInitialized(); 74367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return instrumentation; 75367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 76367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 774c349e0101d6b7a1057722aeb21d7955b0eb0aebKevin Jin public static Context getTargetContext() { 784c349e0101d6b7a1057722aeb21d7955b0eb0aebKevin Jin return getInstrumentation().getTargetContext(); 794c349e0101d6b7a1057722aeb21d7955b0eb0aebKevin Jin } 804c349e0101d6b7a1057722aeb21d7955b0eb0aebKevin Jin 81367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 82367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Gets the <a href= "http://developer.android.com/tools/testing/testing_otheride.html#AMOptionsSyntax" 83367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * >am instrument options</a>. 84367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 85367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static Bundle getOptions() { 86367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin checkInitialized(); 87367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return options; 88367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 89367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 90367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 91367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Gets the string value associated with the given key. This is preferred over using {@link 92367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * #getOptions} because the returned {@link Bundle} contains only string values - am instrument 93367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * options do not support value types other than string. 94367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 95367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static String getOption(String key) { 96367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return getOptions().getString(key); 97367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 98367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 99367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 100367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Calls {@link #getOption} with "dd." prefixed to {@code key}. This is for DroidDriver 101367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * implementation to use a consistent pattern for its options. 102367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 103367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static String getD2Option(String key) { 104367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return getOption("dd." + key); 105367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 106367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 107367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 108367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Tries to wait for an idle state on the main thread on best-effort basis up to {@code 109367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * timeoutMillis}. The main thread may not enter the idle state when animation is playing, for 110367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * example, the ProgressBar. 111367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 112367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static boolean tryWaitForIdleSync(long timeoutMillis) { 113367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin validateNotAppThread(); 114367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin FutureTask<Void> emptyTask = new FutureTask<Void>(EMPTY_RUNNABLE, null); 115367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin instrumentation.waitForIdle(emptyTask); 116367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 117367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin try { 118367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin emptyTask.get(timeoutMillis, TimeUnit.MILLISECONDS); 119367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } catch (java.util.concurrent.TimeoutException e) { 120367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin Logs.log(Log.INFO, 121367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin "Timed out after " + timeoutMillis + " milliseconds waiting for idle on main looper"); 122367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return false; 1231dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin } catch (Throwable t) { 1241dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin throw DroidDriverException.propagate(t); 125367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 126367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return true; 127367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 128367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 129367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static void runOnMainSyncWithTimeout(final Runnable runnable) { 130367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin runOnMainSyncWithTimeout(new Callable<Void>() { 131367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin @Override 132367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public Void call() throws Exception { 133367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin runnable.run(); 134367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return null; 135367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 136367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin }); 137367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 138367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 139367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin /** 140367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * Runs {@code callable} on the main thread on best-effort basis up to a time limit, which 141367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * defaults to {@code 10000L} and can be set as an am instrument option under the key {@code 142367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin * dd.runOnMainSyncTimeout}. <p>This is a safer variation of {@link Instrumentation#runOnMainSync} 1431dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin * because the latter may hang. You may turn off this behavior by setting {@code "-e 1441dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin * dd.runOnMainSyncTimeout 0"} on the am command line.</p>The {@code callable} may never run, for 1451dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin * example, if the main Looper has exited due to uncaught exception. 146367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin */ 147367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public static <V> V runOnMainSyncWithTimeout(Callable<V> callable) { 148367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin validateNotAppThread(); 149367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin final RunOnMainSyncFutureTask<V> futureTask = new RunOnMainSyncFutureTask<>(callable); 150367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 151367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (runOnMainSyncTimeoutMillis <= 0L) { 152367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin // Call runOnMainSync on current thread without time limit. 153367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin futureTask.runOnMainSyncNoThrow(); 154367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } else { 1551dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin RUN_ON_MAIN_SYNC_EXECUTOR.execute(new Runnable() { 156367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin @Override 157367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public void run() { 158367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin futureTask.runOnMainSyncNoThrow(); 159367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 1601dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin }); 161367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 162367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 163367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin try { 164367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin return futureTask.get(runOnMainSyncTimeoutMillis, TimeUnit.MILLISECONDS); 165367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } catch (java.util.concurrent.TimeoutException e) { 166367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new TimeoutException("Timed out after " + runOnMainSyncTimeoutMillis 167367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin + " milliseconds waiting for Instrumentation.runOnMainSync", e); 1681dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin } catch (Throwable t) { 1691dbff05bf375557ea73e07632d732bd90a71af9eKevin Jin throw DroidDriverException.propagate(t); 170367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } finally { 171367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin futureTask.cancel(false); 172367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 173367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 174367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 175367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static class RunOnMainSyncFutureTask<V> extends FutureTask<V> { 176367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public RunOnMainSyncFutureTask(Callable<V> callable) { 177367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin super(callable); 178367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 179367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 180367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin public void runOnMainSyncNoThrow() { 181367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin try { 182367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin getInstrumentation().runOnMainSync(this); 183367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } catch (Throwable e) { 184367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin setException(e); 185367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 186367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 187367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 188367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin 189367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin private static void validateNotAppThread() { 190367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin if (Looper.myLooper() == Looper.getMainLooper()) { 191367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin throw new DroidDriverException( 192367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin "This method can not be called from the main application thread"); 193367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 194367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin } 195367267b01bcc1ec5965cfc7c26149ccd405c11cfKevin Jin} 196