content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
   * except in compliance with the License. You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software distributed under the
   * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied. See the License for the specific language governing
   * permissions and limitations under the License.
   */
  
  package android.os;
  
  import android.util.ArraySet;
  
  import java.util.concurrent.LinkedBlockingQueue;
  
  /**
   * Blocks a looper from executing any messages, and allows the holder of this object
   * to control when and which messages get executed until it is released.
   * <p>
   * A TestLooperManager should be acquired using
   * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
   * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
   * The test code may use {@link #next()} to acquire messages that have been queued to this
   * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
   */
  public class TestLooperManager {
  
      private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
  
      private final MessageQueue mQueue;
      private final Looper mLooper;
      private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
  
      private boolean mReleased;
      private boolean mLooperBlocked;
  
      /**
       * @hide
       */
      public TestLooperManager(Looper looper) {
          synchronized (sHeldLoopers) {
              if (sHeldLoopers.contains(looper)) {
                  throw new RuntimeException("TestLooperManager already held for this looper");
              }
              sHeldLoopers.add(looper);
          }
          mLooper = looper;
          mQueue = mLooper.getQueue();
          // Post a message that will keep the looper blocked as long as we are dispatching.
          new Handler(looper).post(new LooperHolder());
      }
  
      /**
       * Returns the {@link MessageQueue} this object is wrapping.
       */
      public MessageQueue getMessageQueue() {
          checkReleased();
          return mQueue;
      }
  
      /** @removed */
      @Deprecated
      public MessageQueue getQueue() {
          return getMessageQueue();
      }
  
      /**
       * Returns the next message that should be executed by this queue, may block
       * if no messages are ready.
       * <p>
       * Callers should always call {@link #recycle(Message)} on the message when all
       * interactions with it have completed.
       */
      public Message next() {
          // Wait for the looper block to come up, to make sure we don't accidentally get
          // the message for the block.
          while (!mLooperBlocked) {
              synchronized (this) {
                  try {
                      wait();
                  } catch (InterruptedException e) {
                  }
              }
          }
          checkReleased();
          return mQueue.next();
      }
  
      /**
       * Releases the looper to continue standard looping and processing of messages,
       * no further interactions with TestLooperManager will be allowed after
       * release() has been called.
       */
      public void release() {
          synchronized (sHeldLoopers) {
              sHeldLoopers.remove(mLooper);
          }
          checkReleased();
          mReleased = true;
          mExecuteQueue.add(new MessageExecution());
      }
  
      /**
       * Executes the given message on the Looper thread this wrapper is
       * attached to.
       * <p>
       * Execution will happen on the Looper's thread (whether it is the current thread
       * or not), but all RuntimeExceptions encountered while executing the message will
       * be thrown on the calling thread.
       */
      public void execute(Message message) {
          checkReleased();
          if (Looper.myLooper() == mLooper) {
              // This is being called from the thread it should be executed on, we can just dispatch.
              message.target.dispatchMessage(message);
          } else {
              MessageExecution execution = new MessageExecution();
              execution.m = message;
              synchronized (execution) {
                  mExecuteQueue.add(execution);
                  // Wait for the message to be executed.
                  try {
                      execution.wait();
                  } catch (InterruptedException e) {
                  }
                  if (execution.response != null) {
                      throw new RuntimeException(execution.response);
                  }
              }
          }
      }
  
      /**
       * Called to indicate that a Message returned by {@link #next()} has been parsed
       * and should be recycled.
       */
      public void recycle(Message msg) {
          checkReleased();
          msg.recycleUnchecked();
      }
  
      /**
       * Returns true if there are any queued messages that match the parameters.
       *
       * @param h      the value of {@link Message#getTarget()}
       * @param what   the value of {@link Message#what}
       * @param object the value of {@link Message#obj}, null for any
       */
      public boolean hasMessages(Handler h, Object object, int what) {
          checkReleased();
          return mQueue.hasMessages(h, what, object);
      }
  
      /**
       * Returns true if there are any queued messages that match the parameters.
       *
       * @param h      the value of {@link Message#getTarget()}
       * @param r      the value of {@link Message#getCallback()}
       * @param object the value of {@link Message#obj}, null for any
       */
      public boolean hasMessages(Handler h, Object object, Runnable r) {
          checkReleased();
          return mQueue.hasMessages(h, r, object);
      }
  
      private void checkReleased() {
          if (mReleased) {
              throw new RuntimeException("release() has already be called");
          }
      }
  
      private class LooperHolder implements Runnable {
          @Override
          public void run() {
              synchronized (TestLooperManager.this) {
                  mLooperBlocked = true;
                  TestLooperManager.this.notify();
              }
              while (!mReleased) {
                  try {
                      final MessageExecution take = mExecuteQueue.take();
                      if (take.m != null) {
                          processMessage(take);
                      }
                  } catch (InterruptedException e) {
                  }
              }
              synchronized (TestLooperManager.this) {
                  mLooperBlocked = false;
              }
          }
  
          private void processMessage(MessageExecution mex) {
              synchronized (mex) {
                  try {
                      mex.m.target.dispatchMessage(mex.m);
                      mex.response = null;
                  } catch (Throwable t) {
                      mex.response = t;
                  }
                  mex.notifyAll();
              }
          }
      }
  
      private static class MessageExecution {
          private Message m;
          private Throwable response;
      }
  }
  