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