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