Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package android.os;
     16 
     17 import android.util.ArraySet;
     18 
     19 import java.util.concurrent.LinkedBlockingQueue;
     20 
     21 /**
     22  * Blocks a looper from executing any messages, and allows the holder of this object
     23  * to control when and which messages get executed until it is released.
     24  * <p>
     25  * A TestLooperManager should be acquired using
     26  * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
     27  * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
     28  * The test code may use {@link #next()} to acquire messages that have been queued to this
     29  * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
     30  */
     31 public class TestLooperManager {
     32 
     33     private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
     34 
     35     private final MessageQueue mQueue;
     36     private final Looper mLooper;
     37     private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
     38 
     39     private boolean mReleased;
     40     private boolean mLooperBlocked;
     41 
     42     /**
     43      * @hide
     44      */
     45     public TestLooperManager(Looper looper) {
     46         synchronized (sHeldLoopers) {
     47             if (sHeldLoopers.contains(looper)) {
     48                 throw new RuntimeException("TestLooperManager already held for this looper");
     49             }
     50             sHeldLoopers.add(looper);
     51         }
     52         mLooper = looper;
     53         mQueue = mLooper.getQueue();
     54         // Post a message that will keep the looper blocked as long as we are dispatching.
     55         new Handler(looper).post(new LooperHolder());
     56     }
     57 
     58     /**
     59      * Returns the {@link MessageQueue} this object is wrapping.
     60      */
     61     public MessageQueue getMessageQueue() {
     62         checkReleased();
     63         return mQueue;
     64     }
     65 
     66     /** @removed */
     67     @Deprecated
     68     public MessageQueue getQueue() {
     69         return getMessageQueue();
     70     }
     71 
     72     /**
     73      * Returns the next message that should be executed by this queue, may block
     74      * if no messages are ready.
     75      * <p>
     76      * Callers should always call {@link #recycle(Message)} on the message when all
     77      * interactions with it have completed.
     78      */
     79     public Message next() {
     80         // Wait for the looper block to come up, to make sure we don't accidentally get
     81         // the message for the block.
     82         while (!mLooperBlocked) {
     83             synchronized (this) {
     84                 try {
     85                     wait();
     86                 } catch (InterruptedException e) {
     87                 }
     88             }
     89         }
     90         checkReleased();
     91         return mQueue.next();
     92     }
     93 
     94     /**
     95      * Releases the looper to continue standard looping and processing of messages,
     96      * no further interactions with TestLooperManager will be allowed after
     97      * release() has been called.
     98      */
     99     public void release() {
    100         synchronized (sHeldLoopers) {
    101             sHeldLoopers.remove(mLooper);
    102         }
    103         checkReleased();
    104         mReleased = true;
    105         mExecuteQueue.add(new MessageExecution());
    106     }
    107 
    108     /**
    109      * Executes the given message on the Looper thread this wrapper is
    110      * attached to.
    111      * <p>
    112      * Execution will happen on the Looper's thread (whether it is the current thread
    113      * or not), but all RuntimeExceptions encountered while executing the message will
    114      * be thrown on the calling thread.
    115      */
    116     public void execute(Message message) {
    117         checkReleased();
    118         if (Looper.myLooper() == mLooper) {
    119             // This is being called from the thread it should be executed on, we can just dispatch.
    120             message.target.dispatchMessage(message);
    121         } else {
    122             MessageExecution execution = new MessageExecution();
    123             execution.m = message;
    124             synchronized (execution) {
    125                 mExecuteQueue.add(execution);
    126                 // Wait for the message to be executed.
    127                 try {
    128                     execution.wait();
    129                 } catch (InterruptedException e) {
    130                 }
    131                 if (execution.response != null) {
    132                     throw new RuntimeException(execution.response);
    133                 }
    134             }
    135         }
    136     }
    137 
    138     /**
    139      * Called to indicate that a Message returned by {@link #next()} has been parsed
    140      * and should be recycled.
    141      */
    142     public void recycle(Message msg) {
    143         checkReleased();
    144         msg.recycleUnchecked();
    145     }
    146 
    147     /**
    148      * Returns true if there are any queued messages that match the parameters.
    149      *
    150      * @param h      the value of {@link Message#getTarget()}
    151      * @param what   the value of {@link Message#what}
    152      * @param object the value of {@link Message#obj}, null for any
    153      */
    154     public boolean hasMessages(Handler h, Object object, int what) {
    155         checkReleased();
    156         return mQueue.hasMessages(h, what, object);
    157     }
    158 
    159     /**
    160      * Returns true if there are any queued messages that match the parameters.
    161      *
    162      * @param h      the value of {@link Message#getTarget()}
    163      * @param r      the value of {@link Message#getCallback()}
    164      * @param object the value of {@link Message#obj}, null for any
    165      */
    166     public boolean hasMessages(Handler h, Object object, Runnable r) {
    167         checkReleased();
    168         return mQueue.hasMessages(h, r, object);
    169     }
    170 
    171     private void checkReleased() {
    172         if (mReleased) {
    173             throw new RuntimeException("release() has already be called");
    174         }
    175     }
    176 
    177     private class LooperHolder implements Runnable {
    178         @Override
    179         public void run() {
    180             synchronized (TestLooperManager.this) {
    181                 mLooperBlocked = true;
    182                 TestLooperManager.this.notify();
    183             }
    184             while (!mReleased) {
    185                 try {
    186                     final MessageExecution take = mExecuteQueue.take();
    187                     if (take.m != null) {
    188                         processMessage(take);
    189                     }
    190                 } catch (InterruptedException e) {
    191                 }
    192             }
    193             synchronized (TestLooperManager.this) {
    194                 mLooperBlocked = false;
    195             }
    196         }
    197 
    198         private void processMessage(MessageExecution mex) {
    199             synchronized (mex) {
    200                 try {
    201                     mex.m.target.dispatchMessage(mex.m);
    202                     mex.response = null;
    203                 } catch (Throwable t) {
    204                     mex.response = t;
    205                 }
    206                 mex.notifyAll();
    207             }
    208         }
    209     }
    210 
    211     private static class MessageExecution {
    212         private Message m;
    213         private Throwable response;
    214     }
    215 }
    216