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 static com.google.common.base.Preconditions.checkArgument;
20f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.common.base.Preconditions.checkNotNull;
21f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.common.base.Preconditions.checkState;
22f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
23f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.IdlingPolicies;
24f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.IdlingPolicy;
25f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.IdlingResource;
26f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.IdlingResource.ResourceCallback;
27f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.collect.Lists;
28f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
29f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Handler;
30f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Looper;
31f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Message;
32f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.util.Log;
33f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
34f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.BitSet;
35f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.List;
36f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
37f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport javax.inject.Inject;
38f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport javax.inject.Singleton;
39f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
40f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/**
41f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Keeps track of user-registered {@link IdlingResource}s.
42f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */
43f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev@Singleton
44f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpublic final class IdlingResourceRegistry {
45f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final String TAG = IdlingResourceRegistry.class.getSimpleName();
46f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
47f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final int DYNAMIC_RESOURCE_HAS_IDLED = 1;
48f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final int TIMEOUT_OCCURRED = 2;
49f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final int IDLE_WARNING_REACHED = 3;
50f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final int POSSIBLE_RACE_CONDITION_DETECTED = 4;
51f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final Object TIMEOUT_MESSAGE_TAG = new Object();
52f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
53f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private static final IdleNotificationCallback NO_OP_CALLBACK = new IdleNotificationCallback() {
54f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
55f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @Override
56f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public void allResourcesIdle() {}
57f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
58f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @Override
59f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public void resourcesStillBusyWarning(List<String> busys) {}
60f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
61f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @Override
62f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public void resourcesHaveTimedOut(List<String> busys) {}
63f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  };
64f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
65f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  // resources and idleState should only be accessed on main thread
66f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private final List<IdlingResource> resources = Lists.newArrayList();
67f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  // idleState.get(i) == true indicates resources.get(i) is idle, false indicates it's busy
68f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private final BitSet idleState = new BitSet();
69f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private final Looper looper;
70f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private final Handler handler;
71f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private final Dispatcher dispatcher;
72f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private IdleNotificationCallback idleNotificationCallback = NO_OP_CALLBACK;
73f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
74f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  @Inject
75f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  public IdlingResourceRegistry(Looper looper) {
76f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    this.looper = looper;
77f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    this.dispatcher = new Dispatcher();
78f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    this.handler = new Handler(looper, dispatcher);
79f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
80f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
81f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  /**
82f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Registers the given resource.
83f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   */
84f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  public void register(final IdlingResource resource) {
85f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkNotNull(resource);
86f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    if (Looper.myLooper() != looper) {
87f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      handler.post(new Runnable() {
88f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        @Override
89f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        public void run() {
90f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          register(resource);
91f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
92f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      });
93f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } else {
94f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      for (IdlingResource oldResource : resources) {
95f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        if (resource.getName().equals(oldResource.getName())) {
96f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          // This does not throw an error to avoid leaving tests that register resource in test
97f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          // setup in an undeterministic state (we cannot assume that everyone clears vm state
98f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          // between each test run)
99f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          Log.e(TAG, String.format("Attempted to register resource with same names:" +
100f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              " %s. R1: %s R2: %s.\nDuplicate resource registration will be ignored.",
101f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              resource.getName(), resource, oldResource));
102f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          return;
103f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
104f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
105f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      resources.add(resource);
106f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      final int position = resources.size() - 1;
107f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      registerToIdleCallback(resource, position);
108f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      idleState.set(position, resource.isIdleNow());
109f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
110f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
111f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
112f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  public void registerLooper(Looper looper, boolean considerWaitIdle) {
113f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkNotNull(looper);
114f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkArgument(Looper.getMainLooper() != looper, "Not intended for use with main looper!");
115f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    register(new LooperIdlingResource(looper, considerWaitIdle));
116f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
117f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
118f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private void registerToIdleCallback(IdlingResource resource, final int position) {
119f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    resource.registerIdleTransitionCallback(new ResourceCallback() {
120f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      @Override
121f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      public void onTransitionToIdle() {
122f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        Message m = handler.obtainMessage(DYNAMIC_RESOURCE_HAS_IDLED);
123f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        m.arg1 = position;
124f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        handler.sendMessage(m);
125f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
126f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    });
127f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
128f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
129f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  boolean allResourcesAreIdle() {
130f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkState(Looper.myLooper() == looper);
131f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    for (int i = idleState.nextSetBit(0); i >= 0 && i < resources.size();
132f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        i = idleState.nextSetBit(i + 1)) {
133f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      idleState.set(i, resources.get(i).isIdleNow());
134f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
135f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    return idleState.cardinality() == resources.size();
136f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
137f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
138f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  interface IdleNotificationCallback {
139f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public void allResourcesIdle();
140f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
141f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public void resourcesStillBusyWarning(List<String> busyResourceNames);
142f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
143f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public void resourcesHaveTimedOut(List<String> busyResourceNames);
144f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
145f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
146f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  void notifyWhenAllResourcesAreIdle(IdleNotificationCallback callback) {
147f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkNotNull(callback);
148f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkState(Looper.myLooper() == looper);
149f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    checkState(idleNotificationCallback == NO_OP_CALLBACK, "Callback has already been registered.");
150f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    if (allResourcesAreIdle()) {
151f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      callback.allResourcesIdle();
152f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } else {
153f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      idleNotificationCallback = callback;
154f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      scheduleTimeoutMessages();
155f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
156f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
157f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
158f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  void cancelIdleMonitor() {
159f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    dispatcher.deregister();
160f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
161f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
162f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private void scheduleTimeoutMessages() {
163f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    IdlingPolicy warning = IdlingPolicies.getDynamicIdlingResourceWarningPolicy();
164f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    Message timeoutWarning = handler.obtainMessage(IDLE_WARNING_REACHED, TIMEOUT_MESSAGE_TAG);
165f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    handler.sendMessageDelayed(timeoutWarning, warning.getIdleTimeoutUnit().toMillis(
166f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        warning.getIdleTimeout()));
167f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    Message timeoutError = handler.obtainMessage(TIMEOUT_OCCURRED, TIMEOUT_MESSAGE_TAG);
168f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    IdlingPolicy error = IdlingPolicies.getDynamicIdlingResourceErrorPolicy();
169f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
170f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    handler.sendMessageDelayed(timeoutError, error.getIdleTimeoutUnit().toMillis(
171f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        error.getIdleTimeout()));
172f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
173f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
174f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private List<String> getBusyResources() {
175f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    List<String> busyResourceNames = Lists.newArrayList();
176f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    List<Integer> racyResources = Lists.newArrayList();
177f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
178f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    for (int i = 0; i < resources.size(); i++) {
179f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      IdlingResource resource = resources.get(i);
180f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      if (!idleState.get(i)) {
181f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        if (resource.isIdleNow()) {
182f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          // We have not been notified of a BUSY -> IDLE transition, but the resource is telling us
183f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          // its that its idle. Either it's a race condition or is this resource buggy.
184f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          racyResources.add(i);
185f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        } else {
186f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          busyResourceNames.add(resource.getName());
187f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
188f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
189f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
190f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
191f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    if (!racyResources.isEmpty()) {
192f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      Message raceBuster = handler.obtainMessage(POSSIBLE_RACE_CONDITION_DETECTED,
193f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          TIMEOUT_MESSAGE_TAG);
194f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      raceBuster.obj = racyResources;
195f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      handler.sendMessage(raceBuster);
196f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      return null;
197f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    } else {
198f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      return busyResourceNames;
199f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
200f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
201f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
202f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
203f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  private class Dispatcher implements Handler.Callback {
204f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @Override
205f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public boolean handleMessage(Message m) {
206f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      switch (m.what) {
207f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        case DYNAMIC_RESOURCE_HAS_IDLED:
208f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          handleResourceIdled(m);
209f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          break;
210f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        case IDLE_WARNING_REACHED:
211f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          handleTimeoutWarning();
212f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          break;
213f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        case TIMEOUT_OCCURRED:
214f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          handleTimeout();
215f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          break;
216f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        case POSSIBLE_RACE_CONDITION_DETECTED:
217f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          handleRaceCondition(m);
218f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          break;
219f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        default:
220f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          Log.w(TAG, "Unknown message type: " + m);
221f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          return false;
222f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
223f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      return true;
224f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
225f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
226f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    private void handleResourceIdled(Message m) {
227f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      idleState.set(m.arg1, true);
228f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      if (idleState.cardinality() == resources.size()) {
229f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        try {
230f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          idleNotificationCallback.allResourcesIdle();
231f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        } finally {
232f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          deregister();
233f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
234f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
235f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
236f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
237f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    private void handleTimeoutWarning() {
238f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      List<String> busyResources = getBusyResources();
239f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      if (busyResources == null) {
240f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // null indicates that there is either a race or a programming error
241f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // a race detector message has been inserted into the q.
242f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // reinsert the idle_warning_reached message into the q directly after it
243f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // so we generate warnings if the system is still sane.
244f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        handler.sendMessage(handler.obtainMessage(IDLE_WARNING_REACHED, TIMEOUT_MESSAGE_TAG));
245f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      } else {
246f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        IdlingPolicy warning = IdlingPolicies.getDynamicIdlingResourceWarningPolicy();
247f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        idleNotificationCallback.resourcesStillBusyWarning(busyResources);
248f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        handler.sendMessageDelayed(
249f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            handler.obtainMessage(IDLE_WARNING_REACHED, TIMEOUT_MESSAGE_TAG),
250f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev            warning.getIdleTimeoutUnit().toMillis(warning.getIdleTimeout()));
251f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
252f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
253f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
254f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    private void handleTimeout() {
255f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      List<String> busyResources = getBusyResources();
256f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      if (busyResources == null) {
257f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // detected a possible race... we've enqueued a race busting message
258f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // so either that'll resolve the race or kill the app because it's buggy.
259f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        // if the race resolves, we need to timeout properly.
260f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        handler.sendMessage(handler.obtainMessage(TIMEOUT_OCCURRED, TIMEOUT_MESSAGE_TAG));
261f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      } else {
262f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        try {
263f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          idleNotificationCallback.resourcesHaveTimedOut(busyResources);
264f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        } finally {
265f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          deregister();
266f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
267f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
268f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
269f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
270f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @SuppressWarnings("unchecked")
271f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    private void handleRaceCondition(Message m) {
272f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      for (Integer i : (List<Integer>) m.obj) {
273f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        if (idleState.get(i)) {
274f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          // it was a race... i is now idle, everything is fine...
275f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        } else {
276f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          throw new IllegalStateException(String.format(
277f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              "Resource %s isIdleNow() is returning true, but a message indicating that the "
278f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              + "resource has transitioned from busy to idle was never sent.",
279f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev              resources.get(i).getName()));
280f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        }
281f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
282f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
283f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
284f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    private void deregister() {
285f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      handler.removeCallbacksAndMessages(TIMEOUT_MESSAGE_TAG);
286f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      idleNotificationCallback = NO_OP_CALLBACK;
287f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
288f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
289f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev}
290