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