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.os.Build;
20import android.view.View;
21
22import java.lang.reflect.Field;
23import java.lang.reflect.InvocationTargetException;
24import java.lang.reflect.Method;
25import java.util.Arrays;
26import java.util.List;
27
28import io.appium.droiddriver.exceptions.DroidDriverException;
29
30/**
31 * Class to find the root view.
32 */
33public class RootFinder {
34
35  private static final String VIEW_FIELD_NAME = "mViews";
36  private static final Field viewsField;
37  private static final Object windowManagerObj;
38
39  static {
40    String windowManagerClassName =
41        Build.VERSION.SDK_INT >= 17 ? "android.view.WindowManagerGlobal"
42            : "android.view.WindowManagerImpl";
43    String instanceMethod = Build.VERSION.SDK_INT >= 17 ? "getInstance" : "getDefault";
44    try {
45      Class<?> clazz = Class.forName(windowManagerClassName);
46      Method getMethod = clazz.getMethod(instanceMethod);
47      windowManagerObj = getMethod.invoke(null);
48      viewsField = clazz.getDeclaredField(VIEW_FIELD_NAME);
49      viewsField.setAccessible(true);
50    } catch (InvocationTargetException ite) {
51      throw new DroidDriverException(String.format("could not invoke: %s on %s", instanceMethod,
52          windowManagerClassName), ite.getCause());
53    } catch (ClassNotFoundException cnfe) {
54      throw new DroidDriverException(String.format("could not find class: %s",
55          windowManagerClassName), cnfe);
56    } catch (NoSuchFieldException nsfe) {
57      throw new DroidDriverException(String.format("could not find field: %s on %s",
58          VIEW_FIELD_NAME, windowManagerClassName), nsfe);
59    } catch (NoSuchMethodException nsme) {
60      throw new DroidDriverException(String.format("could not find method: %s on %s",
61          instanceMethod, windowManagerClassName), nsme);
62    } catch (RuntimeException re) {
63      throw new DroidDriverException(String.format(
64          "reflective setup failed using obj: %s method: %s field: %s", windowManagerClassName,
65          instanceMethod, VIEW_FIELD_NAME), re);
66    } catch (IllegalAccessException iae) {
67      throw new DroidDriverException(String.format(
68          "reflective setup failed using obj: %s method: %s field: %s", windowManagerClassName,
69          instanceMethod, VIEW_FIELD_NAME), iae);
70    }
71  }
72
73  /**
74   * @return a list of {@link View}s.
75   */
76  @SuppressWarnings("unchecked")
77  public static List<View> getRootViews() {
78    List<View> views = null;
79
80    try {
81      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
82        views = (List<View>) viewsField.get(windowManagerObj);
83      } else {
84        views = Arrays.asList((View[]) viewsField.get(windowManagerObj));
85      }
86      return views;
87    } catch (RuntimeException re) {
88      throw new DroidDriverException(String.format("Reflective access to %s on %s failed.",
89          viewsField, windowManagerObj), re);
90    } catch (IllegalAccessException iae) {
91      throw new DroidDriverException(String.format("Reflective access to %s on %s failed.",
92          viewsField, windowManagerObj), iae);
93    }
94  }
95}
96