gecko/mobile/android/base/tests/components/GeckoViewComponent.java

200 lines
7.2 KiB
Java

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tests.components;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
import org.mozilla.gecko.tests.helpers.*;
import org.mozilla.gecko.tests.UITestContext;
import org.mozilla.gecko.R;
import com.jayway.android.robotium.solo.Condition;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
/**
* A class representing any interactions that take place on GeckoView.
*/
public class GeckoViewComponent extends BaseComponent {
public interface InputConnectionTest {
public void test(InputConnection ic, EditorInfo info);
}
public final TextInput mTextInput;
public GeckoViewComponent(final UITestContext testContext) {
super(testContext);
mTextInput = new TextInput();
}
/**
* Returns the GeckoView.
*/
private View getView() {
// Solo.getView asserts returning a valid View
return mSolo.getView(R.id.layer_view);
}
private void setContext(final Context newContext) {
final View geckoView = getView();
// Switch to a no-InputMethodManager context to avoid interference
mTestContext.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
FrameworkHelper.setViewContext(geckoView, newContext);
}
});
}
public class TextInput {
private TextInput() {
}
private InputMethodManager getInputMethodManager() {
final InputMethodManager imm = (InputMethodManager)
mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
fAssertNotNull("Must have an InputMethodManager", imm);
return imm;
}
/**
* Returns whether text input is being directed to the GeckoView.
*/
private boolean isActive() {
return getInputMethodManager().isActive(getView());
}
public TextInput assertActive() {
fAssertTrue("Current view should be the active input view", isActive());
return this;
}
public TextInput waitForActive() {
WaitHelper.waitFor("current view to become the active input view", new Condition() {
@Override
public boolean isSatisfied() {
return isActive();
}
});
return this;
}
/**
* Returns whether an InputConnection is avaiable.
* An InputConnection is available when text input is being directed to the
* GeckoView, and a text field (input, textarea, contentEditable, etc.) is
* currently focused inside the GeckoView.
*/
private boolean hasInputConnection() {
final InputMethodManager imm = getInputMethodManager();
return imm.isActive(getView()) && imm.isAcceptingText();
}
public TextInput assertInputConnection() {
fAssertTrue("Current view should have an active InputConnection", hasInputConnection());
return this;
}
public TextInput waitForInputConnection() {
WaitHelper.waitFor("current view to have an active InputConnection", new Condition() {
@Override
public boolean isSatisfied() {
return hasInputConnection();
}
});
return this;
}
/**
* Starts an InputConnectionTest. An InputConnectionTest must run on the
* InputConnection thread which may or may not be the main UI thread. Also,
* during an InputConnectionTest, the system InputMethodManager service must
* be temporarily disabled to prevent the system IME from interfering with our
* tests. We disable the service by override the GeckoView's context with one
* that returns a null InputMethodManager service.
*
* @param test Test to run
*/
public TextInput testInputConnection(final InputConnectionTest test) {
fAssertNotNull("Test must not be null", test);
assertInputConnection();
// GeckoInputConnection can run on another thread than the main thread,
// so we need to be testing it on that same thread it's running on
final View geckoView = getView();
final Handler inputConnectionHandler = geckoView.getHandler();
final Context oldGeckoViewContext = FrameworkHelper.getViewContext(geckoView);
setContext(new ContextWrapper(oldGeckoViewContext) {
@Override
public Object getSystemService(String name) {
if (Context.INPUT_METHOD_SERVICE.equals(name)) {
return null;
}
return super.getSystemService(name);
}
});
(new InputConnectionTestRunner(test)).runOnHandler(inputConnectionHandler);
setContext(oldGeckoViewContext);
return this;
}
private class InputConnectionTestRunner implements Runnable {
private final InputConnectionTest mTest;
private boolean mDone;
public InputConnectionTestRunner(final InputConnectionTest test) {
mTest = test;
}
public synchronized void runOnHandler(final Handler inputConnectionHandler) {
// Below, we are blocking the instrumentation thread to wait on the
// InputConnection thread. Therefore, the InputConnection thread must not be
// the same as the instrumentation thread to avoid a deadlock. This should
// always be the case and we perform a sanity check to make sure.
fAssertNotSame("InputConnection should not be running on instrumentation thread",
Looper.myLooper(), inputConnectionHandler.getLooper());
mDone = false;
inputConnectionHandler.post(this);
do {
try {
wait();
} catch (InterruptedException e) {
// Ignore interrupts
}
} while (!mDone);
}
@Override
public void run() {
final EditorInfo info = new EditorInfo();
final InputConnection ic = getView().onCreateInputConnection(info);
fAssertNotNull("Must have an InputConnection", ic);
// Restore the IC to a clean state
ic.clearMetaKeyStates(-1);
ic.finishComposingText();
mTest.test(ic, info);
synchronized (this) {
// Test finished; return from runOnHandler
mDone = true;
notify();
}
}
}
}
}