• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 DroidDriver committers
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.droiddriver.base;
18 
19 import android.app.Instrumentation;
20 import android.os.Looper;
21 import android.util.Log;
22 
23 import com.google.android.droiddriver.exceptions.DroidDriverException;
24 import com.google.android.droiddriver.exceptions.TimeoutException;
25 import com.google.android.droiddriver.finders.ByXPath;
26 import com.google.android.droiddriver.util.Logs;
27 
28 import java.util.Map;
29 import java.util.WeakHashMap;
30 import java.util.concurrent.ExecutionException;
31 import java.util.concurrent.FutureTask;
32 import java.util.concurrent.TimeUnit;
33 
34 /**
35  * Internal helper for DroidDriver implementation.
36  */
37 public class DroidDriverContext<R, E extends BaseUiElement<R, E>> {
38   private final Instrumentation instrumentation;
39   private final BaseDroidDriver<R, E> driver;
40   private final Map<R, E> map;
41 
DroidDriverContext(Instrumentation instrumentation, BaseDroidDriver<R, E> driver)42   public DroidDriverContext(Instrumentation instrumentation, BaseDroidDriver<R, E> driver) {
43     this.instrumentation = instrumentation;
44     this.driver = driver;
45     map = new WeakHashMap<R, E>();
46   }
47 
getInstrumentation()48   public Instrumentation getInstrumentation() {
49     return instrumentation;
50   }
51 
getDriver()52   public BaseDroidDriver<R, E> getDriver() {
53     return driver;
54   }
55 
getElement(R rawElement, E parent)56   public E getElement(R rawElement, E parent) {
57     E element = map.get(rawElement);
58     if (element == null) {
59       element = driver.newUiElement(rawElement, parent);
60       map.put(rawElement, element);
61     }
62     return element;
63   }
64 
newRootElement(R rawRoot)65   public E newRootElement(R rawRoot) {
66     clearData();
67     return getElement(rawRoot, null /* parent */);
68   }
69 
clearData()70   private void clearData() {
71     map.clear();
72     ByXPath.clearData();
73   }
74 
75   /**
76    * Tries to wait for an idle state on the main thread on best-effort basis up
77    * to {@code timeoutMillis}. The main thread may not enter the idle state when
78    * animation is playing, for example, the ProgressBar.
79    */
tryWaitForIdleSync(long timeoutMillis)80   public boolean tryWaitForIdleSync(long timeoutMillis) {
81     validateNotAppThread();
82     FutureTask<?> futureTask = new FutureTask<Void>(new Runnable() {
83       @Override
84       public void run() {}
85     }, null);
86     instrumentation.waitForIdle(futureTask);
87 
88     try {
89       futureTask.get(timeoutMillis, TimeUnit.MILLISECONDS);
90     } catch (InterruptedException e) {
91       throw new DroidDriverException(e);
92     } catch (ExecutionException e) {
93       throw new DroidDriverException(e);
94     } catch (java.util.concurrent.TimeoutException e) {
95       Logs.log(Log.DEBUG, String.format(
96           "Timed out after %d milliseconds waiting for idle on main looper", timeoutMillis));
97       return false;
98     }
99     return true;
100   }
101 
102   /**
103    * Tries to run {@code runnable} on the main thread on best-effort basis up to
104    * {@code timeoutMillis}. The {@code runnable} may never run, for example, in
105    * case that the main Looper has exited due to uncaught exception.
106    */
tryRunOnMainSync(Runnable runnable, long timeoutMillis)107   public boolean tryRunOnMainSync(Runnable runnable, long timeoutMillis) {
108     validateNotAppThread();
109     final FutureTask<?> futureTask = new FutureTask<Void>(runnable, null);
110     new Thread(new Runnable() {
111       @Override
112       public void run() {
113         instrumentation.runOnMainSync(futureTask);
114       }
115     }).start();
116 
117     try {
118       futureTask.get(timeoutMillis, TimeUnit.MILLISECONDS);
119     } catch (InterruptedException e) {
120       throw new DroidDriverException(e);
121     } catch (ExecutionException e) {
122       throw new DroidDriverException(e);
123     } catch (java.util.concurrent.TimeoutException e) {
124       Logs.log(Log.WARN, getRunOnMainSyncTimeoutMessage(timeoutMillis));
125       return false;
126     }
127     return true;
128   }
129 
runOnMainSync(Runnable runnable)130   public void runOnMainSync(Runnable runnable) {
131     long timeoutMillis = getDriver().getPoller().getTimeoutMillis();
132     if (!tryRunOnMainSync(runnable, timeoutMillis)) {
133       throw new TimeoutException(getRunOnMainSyncTimeoutMessage(timeoutMillis));
134     }
135   }
136 
getRunOnMainSyncTimeoutMessage(long timeoutMillis)137   private String getRunOnMainSyncTimeoutMessage(long timeoutMillis) {
138     return String.format(
139         "Timed out after %d milliseconds waiting for Instrumentation.runOnMainSync", timeoutMillis);
140   }
141 
validateNotAppThread()142   private void validateNotAppThread() {
143     if (Looper.myLooper() == Looper.getMainLooper()) {
144       throw new DroidDriverException(
145           "This method can not be called from the main application thread");
146     }
147   }
148 }
149