1f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/*
2f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Copyright (C) 2014 The Android Open Source Project
3f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
4f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Licensed under the Apache License, Version 2.0 (the "License");
5f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * you may not use this file except in compliance with the License.
6f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * You may obtain a copy of the License at
7f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
8f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *   http://www.apache.org/licenses/LICENSE-2.0
9f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
10f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Unless required by applicable law or agreed to in writing, software
11f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * distributed under the License is distributed on an "AS IS" BASIS,
12f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * See the License for the specific language governing permissions and
14f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * limitations under the License.
15f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */
16f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
17f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpackage com.google.android.apps.common.testing.ui.espresso.base;
18f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
19f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.base.Optional;
20f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
21f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Build;
22f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Handler;
23f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Looper;
24f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
25f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.lang.reflect.Field;
26f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.concurrent.Callable;
27f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.concurrent.CountDownLatch;
28f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.concurrent.ExecutionException;
29f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.concurrent.FutureTask;
30f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.concurrent.ThreadPoolExecutor;
31f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
32f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport javax.inject.Inject;
33f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport javax.inject.Singleton;
34f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
35f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/**
36f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Extracts ThreadPoolExecutors used by pieces of android.
37f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
38f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * We do some work to ensure that we load the classes containing these thread pools
39f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * on the main thread, since they may have static initialization that assumes access
40f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * to the main looper.
41f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */
42f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev@Singleton
43f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevfinal class ThreadPoolExecutorExtractor {
44f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final String ASYNC_TASK_CLASS_NAME = "android.os.AsyncTask";
45f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final String MODERN_ASYNC_TASK_CLASS_NAME =
46f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      "android.support.v4.content.ModernAsyncTask";
47f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final String MODERN_ASYNC_TASK_FIELD_NAME = "THREAD_POOL_EXECUTOR";
48f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final String LEGACY_ASYNC_TASK_FIELD_NAME = "sExecutor";
49f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private final Handler mainHandler;
50f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
51f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  @Inject
52f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  ThreadPoolExecutorExtractor(Looper looper) {
53f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    mainHandler = new Handler(looper);
54f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
55f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
56f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
57f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  public ThreadPoolExecutor getAsyncTaskThreadPool() {
58f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    FutureTask<Optional<ThreadPoolExecutor>> getTask = null;
59f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    if (Build.VERSION.SDK_INT < 11) {
60f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      getTask = new FutureTask<Optional<ThreadPoolExecutor>>(LEGACY_ASYNC_TASK_EXECUTOR);
61f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } else {
62f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      getTask = new FutureTask<Optional<ThreadPoolExecutor>>(POST_HONEYCOMB_ASYNC_TASK_EXECUTOR);
63f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
64f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
65f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    try {
66f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      return runOnMainThread(getTask).get().get();
67f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } catch (InterruptedException ie) {
68f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      throw new RuntimeException("Interrupted while trying to get the async task executor!", ie);
69f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } catch (ExecutionException ee) {
70f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      throw new RuntimeException(ee.getCause());
71f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
72f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
73f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
74f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  public Optional<ThreadPoolExecutor> getCompatAsyncTaskThreadPool() {
75f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    try {
76f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      return runOnMainThread(
77f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          new FutureTask<Optional<ThreadPoolExecutor>>(MODERN_ASYNC_TASK_EXTRACTOR)).get();
78f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } catch (InterruptedException ie) {
79f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      throw new RuntimeException("Interrupted while trying to get the compat async executor!", ie);
80f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } catch (ExecutionException ee) {
81f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      throw new RuntimeException(ee.getCause());
82f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
83f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
84f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
85f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private <T> FutureTask<T> runOnMainThread(final FutureTask<T> futureToRun) {
86f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    if (Looper.myLooper() != Looper.getMainLooper()) {
87f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      final CountDownLatch latch = new CountDownLatch(1);
88f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      mainHandler.post(new Runnable() {
89f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        @Override
90f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        public void run() {
91f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          try {
92f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            futureToRun.run();
93f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          } finally {
94f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            latch.countDown();
95f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          }
96f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
97f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      });
98f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      try {
99f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        latch.await();
100f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      } catch (InterruptedException ie) {
101f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        if (!futureToRun.isDone()) {
102f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          throw new RuntimeException("Interrupted while waiting for task to complete.");
103f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
104f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
105f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } else {
106f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      futureToRun.run();
107f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
108f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
109f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    return futureToRun;
110f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
111f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
112f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final Callable<Optional<ThreadPoolExecutor>> MODERN_ASYNC_TASK_EXTRACTOR =
113f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      new Callable<Optional<ThreadPoolExecutor>>() {
114f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        @Override
115f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        public Optional<ThreadPoolExecutor> call() throws Exception {
116f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          try {
117f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            Class<?> modernClazz = Class.forName(MODERN_ASYNC_TASK_CLASS_NAME);
118f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            Field executorField = modernClazz.getField(MODERN_ASYNC_TASK_FIELD_NAME);
119f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            return Optional.of((ThreadPoolExecutor) executorField.get(null));
120f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          } catch (ClassNotFoundException cnfe) {
121f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            return Optional.<ThreadPoolExecutor>absent();
122f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          }
123f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
124f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      };
125f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
126f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final Callable<Class<?>> LOAD_ASYNC_TASK_CLASS =
127f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      new Callable<Class<?>>() {
128f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        @Override
129f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        public Class<?> call() throws Exception {
130f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          return Class.forName(ASYNC_TASK_CLASS_NAME);
131f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
132f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      };
133f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
134f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final Callable<Optional<ThreadPoolExecutor>> LEGACY_ASYNC_TASK_EXECUTOR =
135f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      new Callable<Optional<ThreadPoolExecutor>>() {
136f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        @Override
137f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        public Optional<ThreadPoolExecutor> call() throws Exception {
138f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          Field executorField = LOAD_ASYNC_TASK_CLASS.call()
139f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              .getDeclaredField(LEGACY_ASYNC_TASK_FIELD_NAME);
140f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          executorField.setAccessible(true);
141f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          return Optional.of((ThreadPoolExecutor) executorField.get(null));
142f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
143f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      };
144f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
145f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final Callable<Optional<ThreadPoolExecutor>> POST_HONEYCOMB_ASYNC_TASK_EXECUTOR =
146f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      new Callable<Optional<ThreadPoolExecutor>>() {
147f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        @Override
148f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        public Optional<ThreadPoolExecutor> call() throws Exception {
149f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          Field executorField = LOAD_ASYNC_TASK_CLASS.call()
150f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              .getField(MODERN_ASYNC_TASK_FIELD_NAME);
151f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          return Optional.of((ThreadPoolExecutor) executorField.get(null));
152f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
153f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      };
154f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev}
155