1/* 2 * Copyright (C) 2014 The Android Open Source Project 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 com.google.android.apps.common.testing.ui.espresso.base; 18 19import static com.google.common.base.Preconditions.checkNotNull; 20import static com.google.common.base.Preconditions.checkState; 21 22import com.google.android.apps.common.testing.ui.espresso.IdlingResource; 23import com.google.android.apps.common.testing.ui.espresso.IdlingResource.ResourceCallback; 24import com.google.android.apps.common.testing.ui.espresso.base.QueueInterrogator.QueueState; 25 26import android.os.Handler; 27import android.os.Looper; 28import android.os.MessageQueue.IdleHandler; 29 30/** 31 * An Idling Resource Adapter for Loopers. 32 */ 33final class LooperIdlingResource implements IdlingResource { 34 35 private static final String TAG = "LooperIdleResource"; 36 37 private final boolean considerWaitIdle; 38 private final Looper monitoredLooper; 39 private final Handler monitoredHandler; 40 41 private ResourceCallback resourceCallback; 42 43 LooperIdlingResource(Looper monitoredLooper, boolean considerWaitIdle) { 44 this.monitoredLooper = checkNotNull(monitoredLooper); 45 this.monitoredHandler = new Handler(monitoredLooper); 46 this.considerWaitIdle = considerWaitIdle; 47 checkState(Looper.getMainLooper() != monitoredLooper, "Not for use with main looper."); 48 } 49 50 // Only assigned and read from the main loop. 51 private QueueInterrogator queueInterrogator; 52 53 @Override 54 public String getName() { 55 return monitoredLooper.getThread().getName(); 56 } 57 58 @Override 59 public boolean isIdleNow() { 60 // on main thread here. 61 QueueState state = queueInterrogator.determineQueueState(); 62 boolean idle = state == QueueState.EMPTY || state == QueueState.TASK_DUE_LONG; 63 boolean idleWait = considerWaitIdle 64 && monitoredLooper.getThread().getState() == Thread.State.WAITING; 65 if (idleWait) { 66 if (resourceCallback != null) { 67 resourceCallback.onTransitionToIdle(); 68 } 69 } 70 return idle || idleWait; 71 } 72 73 @Override 74 public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { 75 this.resourceCallback = resourceCallback; 76 // on main thread here. 77 queueInterrogator = new QueueInterrogator(monitoredLooper); 78 79 // must load idle handlers from monitored looper thread. 80 IdleHandler idleHandler = new ResourceCallbackIdleHandler(resourceCallback, queueInterrogator, 81 monitoredHandler); 82 83 checkState(monitoredHandler.postAtFrontOfQueue(new Initializer(idleHandler)), 84 "Monitored looper exiting."); 85 } 86 87 private static class ResourceCallbackIdleHandler implements IdleHandler { 88 private final ResourceCallback resourceCallback; 89 private final QueueInterrogator myInterrogator; 90 private final Handler myHandler; 91 92 ResourceCallbackIdleHandler(ResourceCallback resourceCallback, 93 QueueInterrogator myInterrogator, Handler myHandler) { 94 this.resourceCallback = checkNotNull(resourceCallback); 95 this.myInterrogator = checkNotNull(myInterrogator); 96 this.myHandler = checkNotNull(myHandler); 97 } 98 99 @Override 100 public boolean queueIdle() { 101 // invoked on the monitored looper thread. 102 QueueState queueState = myInterrogator.determineQueueState(); 103 if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) { 104 // no block and no task coming 'shortly'. 105 resourceCallback.onTransitionToIdle(); 106 } else if (queueState == QueueState.BARRIER) { 107 // send a sentinal message that'll cause us to queueIdle again once the 108 // block is lifted. 109 myHandler.sendEmptyMessage(-1); 110 } 111 112 return true; 113 } 114 } 115 116 private static class Initializer implements Runnable { 117 private final IdleHandler myIdleHandler; 118 119 Initializer(IdleHandler myIdleHandler) { 120 this.myIdleHandler = checkNotNull(myIdleHandler); 121 } 122 123 @Override 124 public void run() { 125 // on monitored looper thread. 126 Looper.myQueue().addIdleHandler(myIdleHandler); 127 } 128 } 129 130} 131