Merge "Method to allow testing of Loopers"
This commit is contained in:
committed by
Android (Google) Code Review
commit
d85bf7857d
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
core/java/android/os/TestLooperManager.java
Normal file
209
core/java/android/os/TestLooperManager.java
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user