Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d85bf785 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Method to allow testing of Loopers"

parents 11f5cf93 5be50f7d
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -4844,6 +4844,7 @@ package android.app {
  public class Instrumentation {
    ctor public Instrumentation();
    method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
    method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
    method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
    method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -31560,6 +31561,16 @@ package android.os {
    method public static long uptimeMillis();
  }
  public class TestLooperManager {
    method public void execute(android.os.Message);
    method public android.os.MessageQueue getQueue();
    method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
    method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
    method public android.os.Message next();
    method public void recycle(android.os.Message);
    method public void release();
  }
  public abstract class TokenWatcher {
    ctor public TokenWatcher(android.os.Handler, java.lang.String);
    method public void acquire(android.os.IBinder, java.lang.String);
+11 −0
Original line number Diff line number Diff line
@@ -5013,6 +5013,7 @@ package android.app {
  public class Instrumentation {
    ctor public Instrumentation();
    method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
    method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
    method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
    method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -34321,6 +34322,16 @@ package android.os {
    method public static long uptimeMillis();
  }
  public class TestLooperManager {
    method public void execute(android.os.Message);
    method public android.os.MessageQueue getQueue();
    method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
    method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
    method public android.os.Message next();
    method public void recycle(android.os.Message);
    method public void release();
  }
  public abstract class TokenWatcher {
    ctor public TokenWatcher(android.os.Handler, java.lang.String);
    method public void acquire(android.os.IBinder, java.lang.String);
+11 −0
Original line number Diff line number Diff line
@@ -4854,6 +4854,7 @@ package android.app {
  public class Instrumentation {
    ctor public Instrumentation();
    method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
    method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
    method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
    method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -31683,6 +31684,16 @@ package android.os {
    method public static long uptimeMillis();
  }
  public class TestLooperManager {
    method public void execute(android.os.Message);
    method public android.os.MessageQueue getQueue();
    method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
    method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
    method public android.os.Message next();
    method public void recycle(android.os.Message);
    method public void release();
  }
  public abstract class TokenWatcher {
    ctor public TokenWatcher(android.os.Handler, java.lang.String);
    method public void acquire(android.os.IBinder, java.lang.String);
+26 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.TestLooperManager;
import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Log;
@@ -109,6 +110,22 @@ public class Instrumentation {
    public Instrumentation() {
    }

    /**
     * Called for methods that shouldn't be called by standard apps and
     * should only be used in instrumentation environments. This is not
     * security feature as these classes will still be accessible through
     * reflection, but it will serve as noticeable discouragement from
     * doing such a thing.
     */
    private void checkInstrumenting(String method) {
        // Check if we have an instrumentation context, as init should only get called by
        // the system in startup processes that are being instrumented.
        if (mInstrContext == null) {
            throw new RuntimeException(method +
                    " cannot be called outside of instrumented processes");
        }
    }

    /**
     * Called when the instrumentation is starting, before any application code
     * has been loaded.  Usually this will be implemented to simply call
@@ -2024,6 +2041,15 @@ public class Instrumentation {
        return null;
    }

    /**
     * Takes control of the execution of messages on the specified looper until
     * {@link TestLooperManager#release} is called.
     */
    public TestLooperManager acquireLooperManager(Looper looper) {
        checkInstrumenting("acquireLooperManager");
        return new TestLooperManager(looper);
    }

    private final class InstrumentationThread extends Thread {
        public InstrumentationThread(String name) {
            super(name);
+209 −0
Original line number Diff line number Diff line
/*
 * 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 getQueue() {
        checkReleased();
        return mQueue;
    }

    /**
     * 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;
    }
}