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
17package io.appium.droiddriver.instrumentation;
18
19import android.app.Activity;
20import android.app.Instrumentation;
21import android.os.SystemClock;
22import android.util.Log;
23import android.view.View;
24import io.appium.droiddriver.actions.InputInjector;
25import io.appium.droiddriver.base.BaseDroidDriver;
26import io.appium.droiddriver.base.DroidDriverContext;
27import io.appium.droiddriver.exceptions.NoRunningActivityException;
28import io.appium.droiddriver.util.ActivityUtils;
29import io.appium.droiddriver.util.InstrumentationUtils;
30import io.appium.droiddriver.util.Logs;
31import java.util.List;
32import java.util.concurrent.Callable;
33
34/** Implementation of DroidDriver that is driven via instrumentation. */
35public class InstrumentationDriver extends BaseDroidDriver<View, ViewElement> {
36  private static final Callable<View> FIND_ROOT_VIEW =
37      new Callable<View>() {
38        @Override
39        public View call() {
40          InstrumentationUtils.checkMainThread();
41          Activity runningActivity = ActivityUtils.getRunningActivityNoWait();
42          if (runningActivity == null) {
43            // runningActivity changed since last call!
44            return null;
45          }
46
47          List<View> views = RootFinder.getRootViews();
48          if (views.size() > 1) {
49            Logs.log(Log.VERBOSE, "views.size()=" + views.size());
50            for (View view : views) {
51              if (view.hasWindowFocus()) {
52                return view;
53              }
54            }
55          }
56          // Fall back to DecorView.
57          // TODO(kjin): Should wait until a view hasWindowFocus?
58          return runningActivity.getWindow().getDecorView();
59        }
60      };
61  private final DroidDriverContext<View, ViewElement> context;
62  private final InputInjector injector;
63  private final InstrumentationUiDevice uiDevice;
64
65  public InstrumentationDriver(Instrumentation instrumentation) {
66    context = new DroidDriverContext<View, ViewElement>(instrumentation, this);
67    injector = new InstrumentationInputInjector(instrumentation);
68    uiDevice = new InstrumentationUiDevice(context);
69  }
70
71  @Override
72  public InputInjector getInjector() {
73    return injector;
74  }
75
76  @Override
77  protected ViewElement newRootElement() {
78    return context.newRootElement(findRootView());
79  }
80
81  @Override
82  protected ViewElement newUiElement(View rawElement, ViewElement parent) {
83    return new ViewElement(context, rawElement, parent);
84  }
85
86  private View findRootView() {
87    long timeoutMillis = getPoller().getTimeoutMillis();
88    long end = SystemClock.uptimeMillis() + timeoutMillis;
89    while (true) {
90      long remainingMillis = end - SystemClock.uptimeMillis();
91      if (remainingMillis < 0) {
92        throw new NoRunningActivityException(
93            String.format("Cannot find the running activity after %d milliseconds", timeoutMillis));
94      }
95
96      if (ActivityUtils.getRunningActivity(remainingMillis) != null) {
97        View view = InstrumentationUtils.runOnMainSyncWithTimeout(FIND_ROOT_VIEW);
98        if (view != null) {
99          return view;
100        }
101      }
102    }
103  }
104
105  @Override
106  public InstrumentationUiDevice getUiDevice() {
107    return uiDevice;
108  }
109}
110