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