Commit dcc5d049 authored by zijiehe's avatar zijiehe Committed by Commit bot

[Chromoting] PromisedRaisable in Android client

Some events, such as RenderStub.onClientSizeChanged(), triggers only once during
its lifetime. Though we can ensure to add a ParameterRunnable before the event
is triggered, it seems a little bit inconvenient.
So this change adds a PromisedRaisable Event derived class to ensure newly
added ParameterRunnable can at least be executed once.

BUG=615277

Review-Url: https://codereview.chromium.org/2385593002
Cr-Commit-Position: refs/heads/master@{#423800}
parent 680764f1
......@@ -4,11 +4,14 @@
package org.chromium.chromoting;
import android.os.Handler;
import android.os.Looper;
import java.util.HashSet;
/**
* A thread-safe event queue which provides both {@link #add} and {@link #remove} functions with
* O(log(n)) time complexity, and a {@link raise} function in the derived class
* O(log(n)) time complexity, and a {@link #raise} function in the derived class
* {@link Event.Raisable} to execute all queued callbacks.
*
* @param <ParamT> The parameter used in {@link ParameterRunnable} callback.
......@@ -25,11 +28,11 @@ public class Event<ParamT> {
}
/**
* An event provider version of {@link Event} implementation, provides {@link raise} function to
* execute appended {@link ParameterRunnable}, and {@link clear} function to clear all appended
* callbacks.
* An event provider version of {@link Event} implementation, provides {@link #raise} function
* to execute appended {@link ParameterRunnable}, and {@link #clear} function to clear all
* appended callbacks.
*/
public static final class Raisable<ParamT> extends Event<ParamT> {
public static class Raisable<ParamT> extends Event<ParamT> {
/** Clears all appended callbacks */
public void clear() {
synchronized (mSet) {
......@@ -63,6 +66,58 @@ public class Event<ParamT> {
}
}
/**
* A {@link Raisable} which always executes the newly added {@link ParameterRunnable} with the
* parameter sent to the last {@link #raise} function call. If the event has not been raised,
* this class has consistent behavior as {@link Raisable}. <br>
* This class is useful for some one-time events, such as RenderStub.onClientSizeChanged(). A
* later attached runnable will never be able to get the event. <br>
* The {@link ParameterRunnable} will be executed in the thread in which {@link #add}
* function is called if the event has been raised before. If there is not a {@link Looper} on
* current thread, the runnable will be executed immediately. Otherwise, a task will be posted
* to current looper. Note that it may be executed twice on different threads with the exactly
* same parameter.
*/
public static final class PromisedRaisable<ParamT> extends Raisable<ParamT> {
private boolean mRaised;
private ParamT mLastParameter;
@Override
public Object add(final ParameterRunnable<ParamT> runnable) {
Object result = super.add(runnable);
if (result != null) {
synchronized (mSet) {
if (mRaised) {
if (Looper.myLooper() == null) {
runnable.run(mLastParameter);
} else {
// We should always use the latest parameter, otherwise the order of
// parameters current runnable gets may not be correct.
final PromisedRaisable<ParamT> me = this;
Handler h = new Handler(Looper.myLooper());
h.post(new Runnable() {
@Override
public void run() {
runnable.run(me.mLastParameter);
}
});
}
}
}
}
return result;
}
@Override
public int raise(ParamT parameter) {
synchronized (mSet) {
mRaised = true;
mLastParameter = parameter;
}
return super.raise(parameter);
}
}
/**
* A self removable {@link ParameterRunner}, uses a boolean {@link ParameterCallback} to decide
* whether removes self from {@link Event} or not.
......
......@@ -4,6 +4,8 @@
package org.chromium.chromoting;
import android.os.Handler;
import android.os.Looper;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
......@@ -11,6 +13,9 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.chromium.base.test.util.Feature;
import org.chromium.chromoting.test.util.MutableReference;
import java.util.ArrayList;
import java.util.List;
/**
* Tests for {@link Event}.
*/
......@@ -143,4 +148,148 @@ public class EventTest extends InstrumentationTestCase {
assertFalse(called.get());
assertTrue(event.isEmpty());
}
@SmallTest
@Feature({"Chromoting"})
public static void testPromisedEvent() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
assertNull(Looper.myLooper());
Event.Raisable<Object> event = new Event.PromisedRaisable<>();
final List<Object> called1 = new ArrayList<>();
final List<Object> called2 = new ArrayList<>();
final List<Object> called3 = new ArrayList<>();
final List<Object> called4 = new ArrayList<>();
final List<Object> parameters = new ArrayList<>();
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called1.add(obj);
}
});
Object parameter = new Object();
event.raise(parameter);
parameters.add(parameter);
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called2.add(obj);
}
});
parameter = new Object();
event.raise(parameter);
parameters.add(parameter);
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called3.add(obj);
}
});
parameter = new Object();
event.raise(parameter);
parameters.add(parameter);
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called4.add(obj);
}
});
assertEquals(called1.size(), 3);
assertEquals(called2.size(), 3);
assertEquals(called3.size(), 2);
assertEquals(called4.size(), 1);
for (int i = 0; i < 3; i++) {
assertTrue(called1.get(i) == parameters.get(i));
assertTrue(called2.get(i) == parameters.get(i));
}
for (int i = 0; i < 2; i++) {
assertTrue(called3.get(i) == parameters.get(i + 1));
}
assertTrue(called4.get(0) == parameters.get(2));
}
});
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// Forward exceptions from test thread.
assertFalse(true);
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException ex) { }
}
@SmallTest
@Feature({"Chromoting"})
public static void testPromisedEventWithLooper() {
assertNotNull(Looper.myLooper());
Event.Raisable<Object> event = new Event.PromisedRaisable<>();
final List<Object> called1 = new ArrayList<>();
final List<Object> called2 = new ArrayList<>();
final List<Object> called3 = new ArrayList<>();
final List<Object> called4 = new ArrayList<>();
final List<Object> parameters = new ArrayList<>();
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called1.add(obj);
}
});
Object parameter = new Object();
event.raise(parameter);
parameters.add(parameter);
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called2.add(obj);
}
});
parameter = new Object();
event.raise(parameter);
parameters.add(parameter);
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called3.add(obj);
}
});
parameter = new Object();
event.raise(parameter);
parameters.add(parameter);
event.add(new Event.ParameterRunnable<Object>() {
@Override
public void run(Object obj) {
called4.add(obj);
}
});
Handler h = new Handler(Looper.myLooper());
h.post(new Runnable() {
@Override
public void run() {
Looper.myLooper().quit();
}
});
Looper.loop();
assertEquals(called1.size(), 3);
assertEquals(called2.size(), 3);
assertEquals(called3.size(), 2);
assertEquals(called4.size(), 1);
for (int i = 0; i < 3; i++) {
assertTrue(called1.get(i) == parameters.get(i));
}
assertTrue(called2.get(0) == parameters.get(1));
for (int i = 0; i < 2; i++) {
assertTrue(called2.get(i + 1) == parameters.get(2));
assertTrue(called3.get(i) == parameters.get(2));
}
assertTrue(called4.get(0) == parameters.get(2));
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment