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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.tests.components;
     7 import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull;
     8 import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotSame;
     9 import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
    11 import org.mozilla.gecko.R;
    12 import org.mozilla.gecko.tests.UITestContext;
    13 import org.mozilla.gecko.tests.helpers.FrameworkHelper;
    14 import org.mozilla.gecko.tests.helpers.WaitHelper;
    16 import android.content.Context;
    17 import android.content.ContextWrapper;
    18 import android.os.Handler;
    19 import android.os.Looper;
    20 import android.view.View;
    21 import android.view.inputmethod.EditorInfo;
    22 import android.view.inputmethod.InputConnection;
    23 import android.view.inputmethod.InputMethodManager;
    25 import com.jayway.android.robotium.solo.Condition;
    27 /**
    28  * A class representing any interactions that take place on GeckoView.
    29  */
    30 public class GeckoViewComponent extends BaseComponent {
    32     public interface InputConnectionTest {
    33         public void test(InputConnection ic, EditorInfo info);
    34     }
    36     public final TextInput mTextInput;
    38     public GeckoViewComponent(final UITestContext testContext) {
    39         super(testContext);
    40         mTextInput = new TextInput();
    41     }
    43     /**
    44      * Returns the GeckoView.
    45      */
    46     private View getView() {
    47         // Solo.getView asserts returning a valid View
    48         return mSolo.getView(R.id.layer_view);
    49     }
    51     private void setContext(final Context newContext) {
    52         final View geckoView = getView();
    53         // Switch to a no-InputMethodManager context to avoid interference
    54         mTestContext.getInstrumentation().runOnMainSync(new Runnable() {
    55             @Override
    56             public void run() {
    57                 FrameworkHelper.setViewContext(geckoView, newContext);
    58             }
    59         });
    60     }
    62     public class TextInput {
    63         private TextInput() {
    64         }
    66         private InputMethodManager getInputMethodManager() {
    67             final InputMethodManager imm = (InputMethodManager)
    68                 mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
    69             fAssertNotNull("Must have an InputMethodManager", imm);
    70             return imm;
    71         }
    73         /**
    74          * Returns whether text input is being directed to the GeckoView.
    75          */
    76         private boolean isActive() {
    77             return getInputMethodManager().isActive(getView());
    78         }
    80         public TextInput assertActive() {
    81             fAssertTrue("Current view should be the active input view", isActive());
    82             return this;
    83         }
    85         public TextInput waitForActive() {
    86             WaitHelper.waitFor("current view to become the active input view", new Condition() {
    87                 @Override
    88                 public boolean isSatisfied() {
    89                     return isActive();
    90                 }
    91             });
    92             return this;
    93         }
    95         /**
    96          * Returns whether an InputConnection is avaiable.
    97          * An InputConnection is available when text input is being directed to the
    98          * GeckoView, and a text field (input, textarea, contentEditable, etc.) is
    99          * currently focused inside the GeckoView.
   100          */
   101         private boolean hasInputConnection() {
   102             final InputMethodManager imm = getInputMethodManager();
   103             return imm.isActive(getView()) && imm.isAcceptingText();
   104         }
   106         public TextInput assertInputConnection() {
   107             fAssertTrue("Current view should have an active InputConnection", hasInputConnection());
   108             return this;
   109         }
   111         public TextInput waitForInputConnection() {
   112             WaitHelper.waitFor("current view to have an active InputConnection", new Condition() {
   113                 @Override
   114                 public boolean isSatisfied() {
   115                     return hasInputConnection();
   116                 }
   117             });
   118             return this;
   119         }
   121         /**
   122          * Starts an InputConnectionTest. An InputConnectionTest must run on the
   123          * InputConnection thread which may or may not be the main UI thread. Also,
   124          * during an InputConnectionTest, the system InputMethodManager service must
   125          * be temporarily disabled to prevent the system IME from interfering with our
   126          * tests. We disable the service by override the GeckoView's context with one
   127          * that returns a null InputMethodManager service.
   128          *
   129          * @param test Test to run
   130          */
   131         public TextInput testInputConnection(final InputConnectionTest test) {
   133             fAssertNotNull("Test must not be null", test);
   134             assertInputConnection();
   136             // GeckoInputConnection can run on another thread than the main thread,
   137             // so we need to be testing it on that same thread it's running on
   138             final View geckoView = getView();
   139             final Handler inputConnectionHandler = geckoView.getHandler();
   140             final Context oldGeckoViewContext = FrameworkHelper.getViewContext(geckoView);
   142             setContext(new ContextWrapper(oldGeckoViewContext) {
   143                 @Override
   144                 public Object getSystemService(String name) {
   145                     if (Context.INPUT_METHOD_SERVICE.equals(name)) {
   146                         return null;
   147                     }
   148                     return super.getSystemService(name);
   149                 }
   150             });
   152             (new InputConnectionTestRunner(test)).runOnHandler(inputConnectionHandler);
   154             setContext(oldGeckoViewContext);
   155             return this;
   156         }
   158         private class InputConnectionTestRunner implements Runnable {
   159             private final InputConnectionTest mTest;
   160             private boolean mDone;
   162             public InputConnectionTestRunner(final InputConnectionTest test) {
   163                 mTest = test;
   164             }
   166             public synchronized void runOnHandler(final Handler inputConnectionHandler) {
   167                 // Below, we are blocking the instrumentation thread to wait on the
   168                 // InputConnection thread. Therefore, the InputConnection thread must not be
   169                 // the same as the instrumentation thread to avoid a deadlock. This should
   170                 // always be the case and we perform a sanity check to make sure.
   171                 fAssertNotSame("InputConnection should not be running on instrumentation thread",
   172                     Looper.myLooper(), inputConnectionHandler.getLooper());
   174                 mDone = false;
   175                 inputConnectionHandler.post(this);
   176                 do {
   177                     try {
   178                         wait();
   179                     } catch (InterruptedException e) {
   180                         // Ignore interrupts
   181                     }
   182                 } while (!mDone);
   183             }
   185             @Override
   186             public void run() {
   187                 final EditorInfo info = new EditorInfo();
   188                 final InputConnection ic = getView().onCreateInputConnection(info);
   189                 fAssertNotNull("Must have an InputConnection", ic);
   190                 // Restore the IC to a clean state
   191                 ic.clearMetaKeyStates(-1);
   192                 ic.finishComposingText();
   193                 mTest.test(ic, info);
   194                 synchronized (this) {
   195                     // Test finished; return from runOnHandler
   196                     mDone = true;
   197                     notify();
   198                 }
   199             }
   200         }
   201     }
   202 }

mercurial