Beispiel #1
1
  private static boolean startBrowsingIntent(Context context, String url) {
    Intent intent;
    // Perform generic parsing of the URI to turn it into an Intent.
    try {
      intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
    } catch (Exception ex) {
      Log.w(TAG, "Bad URI %s", url, ex);
      return false;
    }
    // Check for regular URIs that WebView supports by itself, but also
    // check if there is a specialized app that had registered itself
    // for this kind of an intent.
    Matcher m = BROWSER_URI_SCHEMA.matcher(url);
    if (m.matches() && !isSpecializedHandlerAvailable(context, intent)) {
      return false;
    }
    // Sanitize the Intent, ensuring web pages can not bypass browser
    // security (only access to BROWSABLE activities).
    intent.addCategory(Intent.CATEGORY_BROWSABLE);
    intent.setComponent(null);
    Intent selector = intent.getSelector();
    if (selector != null) {
      selector.addCategory(Intent.CATEGORY_BROWSABLE);
      selector.setComponent(null);
    }

    // Pass the package name as application ID so that the intent from the
    // same application can be opened in the same tab.
    intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
    try {
      context.startActivity(intent);
      return true;
    } catch (ActivityNotFoundException ex) {
      Log.w(TAG, "No application can handle %s", url);
    }
    return false;
  }
 private static void tryObtainingDataDirLockOrDie() {
   StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
   StrictMode.allowThreadDiskWrites();
   try {
     String dataPath = PathUtils.getDataDirectory(ContextUtils.getApplicationContext());
     File lockFile = new File(dataPath, EXCLUSIVE_LOCK_FILE);
     boolean success = false;
     try {
       // Note that the file is not closed intentionally.
       RandomAccessFile file = new RandomAccessFile(lockFile, "rw");
       sExclusiveFileLock = file.getChannel().tryLock();
       success = sExclusiveFileLock != null;
     } catch (IOException e) {
       Log.w(TAG, "Failed to create lock file " + lockFile, e);
     }
     if (!success) {
       Log.w(
           TAG,
           "The app may have another WebView opened in a separate process. "
               + "This is not recommended and may stop working in future versions.");
     }
   } finally {
     StrictMode.setThreadPolicy(oldPolicy);
   }
 }
  /** @see BaseInputConnection#setComposingRegion(int, int) */
  @Override
  public boolean setComposingRegion(int start, int end) {
    if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]");
    int textLength = mEditable.length();
    int a = Math.min(start, end);
    int b = Math.max(start, end);
    if (a < 0) a = 0;
    if (b < 0) b = 0;
    if (a > textLength) a = textLength;
    if (b > textLength) b = textLength;

    CharSequence regionText = null;
    if (a == b) {
      removeComposingSpans(mEditable);
    } else {
      if (a == 0 && b == mEditable.length()) {
        regionText = mEditable.subSequence(a, b);
        // If setting composing region that matches, at least in length, of the entire
        // editable region then check it for image placeholders.  If any are found,
        // don't continue this operation.
        // This fixes the problem where, on Android 4.3, pasting an image is followed
        // by setting the composing region which then causes the image to be deleted.
        // http://crbug.com/466755
        for (int i = a; i < b; ++i) {
          if (regionText.charAt(i) == '\uFFFC') return true;
        }
      }
      super.setComposingRegion(a, b);
    }
    updateSelectionIfRequired();

    return mImeAdapter.setComposingRegion(regionText, a, b);
  }
 /** @see BaseInputConnection#endBatchEdit() */
 @Override
 public boolean endBatchEdit() {
   if (mNumNestedBatchEdits == 0) return false;
   --mNumNestedBatchEdits;
   if (DEBUG) Log.w(TAG, "endBatchEdit [" + (mNumNestedBatchEdits == 0) + "]");
   if (mNumNestedBatchEdits == 0) updateSelectionIfRequired();
   return mNumNestedBatchEdits != 0;
 }
 /** @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) */
 @Override
 public boolean setComposingText(CharSequence text, int newCursorPosition) {
   if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPosition + "]");
   if (maybePerformEmptyCompositionWorkaround(text)) return true;
   mPendingAccent = 0;
   super.setComposingText(text, newCursorPosition);
   updateSelectionIfRequired();
   return mImeAdapter.checkCompositionQueueAndCallNative(text, newCursorPosition, false);
 }
 /** @see BaseInputConnection#setSelection(int, int) */
 @Override
 public boolean setSelection(int start, int end) {
   if (DEBUG) Log.w(TAG, "setSelection [" + start + " " + end + "]");
   int textLength = mEditable.length();
   if (start < 0 || end < 0 || start > textLength || end > textLength) return true;
   super.setSelection(start, end);
   updateSelectionIfRequired();
   return mImeAdapter.setEditableSelectionOffsets(start, end);
 }
 /**
  * @see BaseInputConnection#getExtractedText(android.view.inputmethod.ExtractedTextRequest, int)
  */
 @Override
 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
   if (DEBUG) Log.w(TAG, "getExtractedText");
   ExtractedText et = new ExtractedText();
   et.text = mEditable.toString();
   et.partialEndOffset = mEditable.length();
   et.selectionStart = Selection.getSelectionStart(mEditable);
   et.selectionEnd = Selection.getSelectionEnd(mEditable);
   et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0;
   return et;
 }
  /**
   * Updates the AdapterInputConnection's internal representation of the text being edited and its
   * selection and composition properties. The resulting Editable is accessible through the
   * getEditable() method. If the text has not changed, this also calls updateSelection on the
   * InputMethodManager.
   *
   * @param text The String contents of the field being edited.
   * @param selectionStart The character offset of the selection start, or the caret position if
   *     there is no selection.
   * @param selectionEnd The character offset of the selection end, or the caret position if there
   *     is no selection.
   * @param compositionStart The character offset of the composition start, or -1 if there is no
   *     composition.
   * @param compositionEnd The character offset of the composition end, or -1 if there is no
   *     selection.
   * @param isNonImeChange True when the update was caused by non-IME (e.g. Javascript).
   */
  @VisibleForTesting
  public void updateState(
      String text,
      int selectionStart,
      int selectionEnd,
      int compositionStart,
      int compositionEnd,
      boolean isNonImeChange) {
    if (DEBUG) {
      Log.w(
          TAG,
          "updateState ["
              + text
              + "] ["
              + selectionStart
              + " "
              + selectionEnd
              + "] ["
              + compositionStart
              + " "
              + compositionEnd
              + "] ["
              + isNonImeChange
              + "]");
    }
    // If this update is from the IME, no further state modification is necessary because the
    // state should have been updated already by the IM framework directly.
    if (!isNonImeChange) return;

    // Non-breaking spaces can cause the IME to get confused. Replace with normal spaces.
    text = text.replace('\u00A0', ' ');

    selectionStart = Math.min(selectionStart, text.length());
    selectionEnd = Math.min(selectionEnd, text.length());
    compositionStart = Math.min(compositionStart, text.length());
    compositionEnd = Math.min(compositionEnd, text.length());

    String prevText = mEditable.toString();
    boolean textUnchanged = prevText.equals(text);

    if (!textUnchanged) {
      mEditable.replace(0, mEditable.length(), text);
    }

    Selection.setSelection(mEditable, selectionStart, selectionEnd);

    if (compositionStart == compositionEnd) {
      removeComposingSpans(mEditable);
    } else {
      super.setComposingRegion(compositionStart, compositionEnd);
    }
    updateSelectionIfRequired();
  }
 /** @see BaseInputConnection#performContextMenuAction(int) */
 @Override
 public boolean performContextMenuAction(int id) {
   if (DEBUG) Log.w(TAG, "performContextMenuAction [" + id + "]");
   switch (id) {
     case android.R.id.selectAll:
       return mImeAdapter.selectAll();
     case android.R.id.cut:
       return mImeAdapter.cut();
     case android.R.id.copy:
       return mImeAdapter.copy();
     case android.R.id.paste:
       return mImeAdapter.paste();
     default:
       return false;
   }
 }
  /** @see BaseInputConnection#finishComposingText() */
  @Override
  public boolean finishComposingText() {
    if (DEBUG) Log.w(TAG, "finishComposingText");

    mPendingAccent = 0;

    if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) {
      return true;
    }

    super.finishComposingText();
    updateSelectionIfRequired();
    mImeAdapter.finishComposingText();

    return true;
  }
 /** @see BaseInputConnection#performEditorAction(int) */
 @Override
 public boolean performEditorAction(int actionCode) {
   if (DEBUG) Log.w(TAG, "performEditorAction [" + actionCode + "]");
   if (actionCode == EditorInfo.IME_ACTION_NEXT) {
     restartInput();
     // Send TAB key event
     long timeStampMs = SystemClock.uptimeMillis();
     mImeAdapter.sendSyntheticKeyEvent(
         WebInputEventType.RawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0, 0);
   } else {
     mImeAdapter.sendKeyEventWithKeyCode(
         KeyEvent.KEYCODE_ENTER,
         KeyEvent.FLAG_SOFT_KEYBOARD
             | KeyEvent.FLAG_KEEP_TOUCH_MODE
             | KeyEvent.FLAG_EDITOR_ACTION);
   }
   return true;
 }
Beispiel #12
0
  private void requestPermissionsForPage(PermissionRequest request) {
    // Deny any unrecognized permissions.
    for (String webkitPermission : request.getResources()) {
      if (!sPermissions.containsKey(webkitPermission)) {
        Log.w(TAG, "Unrecognized WebKit permission: " + webkitPermission);
        request.deny();
        return;
      }
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      request.grant(request.getResources());
      return;
    }

    // Find what Android permissions we need before we can grant these WebKit permissions.
    ArrayList<String> androidPermissionsNeeded = new ArrayList<String>();
    for (String webkitPermission : request.getResources()) {
      if (!canGrant(webkitPermission)) {
        // We already checked for unrecognized permissions, and canGrant will skip over
        // NO_ANDROID_PERMISSION cases, so this is guaranteed to be a regular Android
        // permission.
        String androidPermission = sPermissions.get(webkitPermission);
        androidPermissionsNeeded.add(androidPermission);
      }
    }

    // If there are no such Android permissions, grant the WebKit permissions immediately.
    if (androidPermissionsNeeded.isEmpty()) {
      request.grant(request.getResources());
      return;
    }

    // Otherwise, file a new request
    if (mNextRequestKey == Integer.MAX_VALUE) {
      Log.e(TAG, "Too many permission requests");
      return;
    }
    int requestCode = mNextRequestKey;
    mNextRequestKey++;
    mPendingRequests.append(requestCode, request);
    requestPermissions(androidPermissionsNeeded.toArray(new String[0]), requestCode);
  }
Beispiel #13
0
  /**
   * * Creates a BluetoothAdapterWrapper using the default android.bluetooth.BluetoothAdapter. May
   * fail if the default adapter is not available or if the application does not have sufficient
   * permissions.
   */
  @CalledByNative
  public static BluetoothAdapterWrapper createWithDefaultAdapter(Context context) {
    final boolean hasPermissions =
        context.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH)
                == PackageManager.PERMISSION_GRANTED
            && context.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_ADMIN)
                == PackageManager.PERMISSION_GRANTED;
    if (!hasPermissions) {
      Log.w(TAG, "BluetoothAdapterWrapper.create failed: Lacking Bluetooth permissions.");
      return null;
    }

    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    if (adapter == null) {
      Log.i(TAG, "BluetoothAdapterWrapper.create failed: Default adapter not found.");
      return null;
    } else {
      Log.i(TAG, "BluetoothAdapterWrapper created with default adapter.");
      return new BluetoothAdapterWrapper(adapter);
    }
  }
  @VisibleForTesting
  protected List<String> getLogcat() throws IOException, InterruptedException {
    // Grab the last lines of the logcat output, with a generous buffer to compensate for any
    // microdumps that might be in the logcat output, since microdumps are stripped in the
    // extraction code. Note that the repeated check of the process exit value is to account for
    // the fact that the process might not finish immediately.  And, it's not appropriate to
    // call p.waitFor(), because this call will block *forever* if the process's output buffer
    // fills up.
    Process p = Runtime.getRuntime().exec("logcat -d");
    BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
    LinkedList<String> rawLogcat = new LinkedList<>();
    Integer exitValue = null;
    try {
      while (exitValue == null) {
        String logLn;
        while ((logLn = reader.readLine()) != null) {
          rawLogcat.add(logLn);
          if (rawLogcat.size() > LOGCAT_SIZE * 4) {
            rawLogcat.removeFirst();
          }
        }

        try {
          exitValue = p.exitValue();
        } catch (IllegalThreadStateException itse) {
          Thread.sleep(HALF_SECOND);
        }
      }
    } finally {
      reader.close();
    }

    if (exitValue != 0) {
      String msg = "Logcat failed: " + exitValue;
      Log.w(TAG, msg);
      throw new IOException(msg);
    }

    return trimLogcat(rawLogcat, LOGCAT_SIZE);
  }
 private static void copy(File src, File dst) throws IOException {
   InputStream in = null;
   OutputStream out = null;
   try {
     in = new FileInputStream(src);
     out = new FileOutputStream(dst);
     // Transfer bytes from in to out
     byte[] buf = new byte[1024];
     int len;
     while ((len = in.read(buf)) > 0) {
       out.write(buf, 0, len);
     }
   } catch (IOException e) {
     Log.w(TAG, e.toString());
   } finally {
     try {
       if (in != null) in.close();
     } finally {
       if (out != null) out.close();
     }
   }
 }
Beispiel #16
0
 /**
  * Replace Android OS's StrictMode.violationsBeingTimed with a custom ArrayList acting as an
  * observer into violation stack traces. Set up an idle handler so StrictMode violations that
  * occur on startup are not ignored.
  */
 @SuppressWarnings({"unchecked", "rawtypes"})
 @UiThread
 private static void initializeStrictModeWatch() {
   try {
     Field violationsBeingTimedField = StrictMode.class.getDeclaredField("violationsBeingTimed");
     violationsBeingTimedField.setAccessible(true);
     ThreadLocal<ArrayList> violationsBeingTimed =
         (ThreadLocal<ArrayList>) violationsBeingTimedField.get(null);
     ArrayList replacementList = new SnoopingArrayList();
     violationsBeingTimed.set(replacementList);
   } catch (Exception e) {
     // Terminate watch if any exceptions are raised.
     Log.w(TAG, "Could not initialize StrictMode watch.", e);
     return;
   }
   sNumUploads.set(0);
   // Delay handling StrictMode violations during initialization until the main loop is idle.
   Looper.myQueue()
       .addIdleHandler(
           new MessageQueue.IdleHandler() {
             @Override
             public boolean queueIdle() {
               // Will retry if the native library has not been initialized.
               if (!LibraryLoader.isInitialized()) return true;
               // Check again next time if no more cached stack traces to upload, and we have not
               // reached the max number of uploads for this session.
               if (sCachedStackTraces.isEmpty()) {
                 // TODO(wnwen): Add UMA count when this happens.
                 // In case of races, continue checking an extra time (equal condition).
                 return sNumUploads.get() <= MAX_UPLOADS_PER_SESSION;
               }
               // Since this is the only place we are removing elements, no need for additional
               // synchronization to ensure it is still non-empty.
               reportStrictModeViolation(sCachedStackTraces.remove(0));
               return true;
             }
           });
 }
  @Override
  public Boolean call() {
    Log.i(TAG, "Trying to extract logcat for minidump");
    try {
      // Step 1: Extract a single logcat file.
      File logcatFile = getElidedLogcat();

      // Step 2: Make copies of logcat file for each  minidump then invoke
      // MinidumpPreparationService on each file pair.
      int len = mMinidumpFilenames.length;
      CrashFileManager fileManager = new CrashFileManager(mContext.getCacheDir());
      for (int i = 0; i < len; i++) {
        // Output crash dump file path to logcat so non-browser crashes appear too.
        Log.i(TAG, "Output crash dump:");
        Log.i(TAG, fileManager.getCrashFile(mMinidumpFilenames[i]).getAbsolutePath());
        processMinidump(logcatFile, mMinidumpFilenames[i], fileManager, i == len - 1);
      }
      return true;
    } catch (IOException | InterruptedException e) {
      Log.w(TAG, e.toString());
      return false;
    }
  }
 private String getPromptText() {
   ProfileSyncService pss = ProfileSyncService.get();
   String accountName = pss.getCurrentSignedInAccountText() + "\n\n";
   PassphraseType passphraseType = pss.getPassphraseType();
   if (pss.hasExplicitPassphraseTime()) {
     switch (passphraseType) {
       case FROZEN_IMPLICIT_PASSPHRASE:
         return accountName + pss.getSyncEnterGooglePassphraseBodyWithDateText();
       case CUSTOM_PASSPHRASE:
         return accountName + pss.getSyncEnterCustomPassphraseBodyWithDateText();
       case IMPLICIT_PASSPHRASE: // Falling through intentionally.
       case KEYSTORE_PASSPHRASE: // Falling through intentionally.
       default:
         Log.w(
             TAG,
             "Found incorrect passphrase type "
                 + passphraseType
                 + ". Falling back to default string.");
         return accountName + pss.getSyncEnterCustomPassphraseBodyText();
     }
   }
   return accountName + pss.getSyncEnterCustomPassphraseBodyText();
 }
 /**
  * Sends selection update to the InputMethodManager unless we are currently in a batch edit or if
  * the exact same selection and composition update was sent already.
  */
 private void updateSelectionIfRequired() {
   if (mNumNestedBatchEdits != 0) return;
   int selectionStart = Selection.getSelectionStart(mEditable);
   int selectionEnd = Selection.getSelectionEnd(mEditable);
   int compositionStart = getComposingSpanStart(mEditable);
   int compositionEnd = getComposingSpanEnd(mEditable);
   // Avoid sending update if we sent an exact update already previously.
   if (mLastUpdateSelectionStart == selectionStart
       && mLastUpdateSelectionEnd == selectionEnd
       && mLastUpdateCompositionStart == compositionStart
       && mLastUpdateCompositionEnd == compositionEnd) {
     return;
   }
   if (DEBUG) {
     Log.w(
         TAG,
         "updateSelectionIfRequired ["
             + selectionStart
             + " "
             + selectionEnd
             + "] ["
             + compositionStart
             + " "
             + compositionEnd
             + "]");
   }
   // updateSelection should be called every time the selection or composition changes
   // if it happens not within a batch edit, or at the end of each top level batch edit.
   getInputMethodManagerWrapper()
       .updateSelection(
           mInternalView, selectionStart, selectionEnd, compositionStart, compositionEnd);
   mLastUpdateSelectionStart = selectionStart;
   mLastUpdateSelectionEnd = selectionEnd;
   mLastUpdateCompositionStart = compositionStart;
   mLastUpdateCompositionEnd = compositionEnd;
 }
 private static void move(File from, File to) {
   if (!from.renameTo(to)) {
     Log.w(TAG, "Fail to rename logcat file");
   }
 }
 /** @see BaseInputConnection#beginBatchEdit() */
 @Override
 public boolean beginBatchEdit() {
   if (DEBUG) Log.w(TAG, "beginBatchEdit [" + (mNumNestedBatchEdits == 0) + "]");
   mNumNestedBatchEdits++;
   return true;
 }
  /** @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) */
  @Override
  public boolean sendKeyEvent(KeyEvent event) {
    if (DEBUG) {
      Log.w(
          TAG,
          "sendKeyEvent ["
              + event.getAction()
              + "] ["
              + event.getKeyCode()
              + "] ["
              + event.getUnicodeChar()
              + "]");
    }

    int action = event.getAction();
    int keycode = event.getKeyCode();
    int unicodeChar = event.getUnicodeChar();

    // If this isn't a KeyDown event, no need to update composition state; just pass the key
    // event through and return. But note that some keys, such as enter, may actually be
    // handled on ACTION_UP in Blink.
    if (action != KeyEvent.ACTION_DOWN) {
      mImeAdapter.translateAndSendNativeEvents(event);
      return true;
    }

    // If this is backspace/del or if the key has a character representation,
    // need to update the underlying Editable (i.e. the local representation of the text
    // being edited).  Some IMEs like Jellybean stock IME and Samsung IME mix in delete
    // KeyPress events instead of calling deleteSurroundingText.
    if (keycode == KeyEvent.KEYCODE_DEL) {
      deleteSurroundingTextImpl(1, 0, true);
    } else if (keycode == KeyEvent.KEYCODE_FORWARD_DEL) {
      deleteSurroundingTextImpl(0, 1, true);
    } else if (keycode == KeyEvent.KEYCODE_ENTER) {
      // Finish text composition when pressing enter, as that may submit a form field.
      // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed.
      finishComposingText();
    } else if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
      // Store a pending accent character and make it the current composition.
      int pendingAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK;
      StringBuilder builder = new StringBuilder();
      builder.appendCodePoint(pendingAccent);
      setComposingText(builder.toString(), 1);
      mPendingAccent = pendingAccent;
      return true;
    } else if (mPendingAccent != 0 && unicodeChar != 0) {
      int combined = KeyEvent.getDeadChar(mPendingAccent, unicodeChar);
      if (combined != 0) {
        StringBuilder builder = new StringBuilder();
        builder.appendCodePoint(combined);
        commitText(builder.toString(), 1);
        return true;
      }
      // Noncombinable character; commit the accent character and fall through to sending
      // the key event for the character afterwards.
      finishComposingText();
    }
    replaceSelectionWithUnicodeChar(unicodeChar);
    mImeAdapter.translateAndSendNativeEvents(event);
    return true;
  }
  private boolean deleteSurroundingTextImpl(
      int beforeLength, int afterLength, boolean fromPhysicalKey) {
    if (DEBUG) {
      Log.w(
          TAG,
          "deleteSurroundingText ["
              + beforeLength
              + " "
              + afterLength
              + " "
              + fromPhysicalKey
              + "]");
    }

    if (mPendingAccent != 0) {
      finishComposingText();
    }

    int originalBeforeLength = beforeLength;
    int originalAfterLength = afterLength;
    int selectionStart = Selection.getSelectionStart(mEditable);
    int selectionEnd = Selection.getSelectionEnd(mEditable);
    int availableBefore = selectionStart;
    int availableAfter = mEditable.length() - selectionEnd;
    beforeLength = Math.min(beforeLength, availableBefore);
    afterLength = Math.min(afterLength, availableAfter);

    // Adjust these values even before calling super.deleteSurroundingText() to be consistent
    // with the super class.
    if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionStart - beforeLength)) {
      beforeLength += 1;
    }
    if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionEnd + afterLength)) {
      afterLength += 1;
    }

    super.deleteSurroundingText(beforeLength, afterLength);
    updateSelectionIfRequired();

    // If this was called due to a physical key, no need to generate a key event here as
    // the caller will take care of forwarding the original.
    if (fromPhysicalKey) {
      return true;
    }

    // For single-char deletion calls |ImeAdapter.sendKeyEventWithKeyCode| with the real key
    // code. For multi-character deletion, executes deletion by calling
    // |ImeAdapter.deleteSurroundingText| and sends synthetic key events with a dummy key code.
    int keyCode = KeyEvent.KEYCODE_UNKNOWN;
    if (originalBeforeLength == 1 && originalAfterLength == 0) {
      keyCode = KeyEvent.KEYCODE_DEL;
    } else if (originalBeforeLength == 0 && originalAfterLength == 1) {
      keyCode = KeyEvent.KEYCODE_FORWARD_DEL;
    }

    boolean result = true;
    if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
      result =
          mImeAdapter.sendSyntheticKeyEvent(
              WebInputEventType.RawKeyDown, SystemClock.uptimeMillis(), keyCode, 0, 0);
      result &= mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
      result &=
          mImeAdapter.sendSyntheticKeyEvent(
              WebInputEventType.KeyUp, SystemClock.uptimeMillis(), keyCode, 0, 0);
    } else {
      mImeAdapter.sendKeyEventWithKeyCode(
          keyCode, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
    }
    return result;
  }
 static void logPidWarning(int pid, String message) {
   // This class is effectively a no-op in single process mode, so don't log warnings there.
   if (pid > 0 && !nativeIsSingleProcess()) {
     Log.w(TAG, message + ", pid=" + pid);
   }
 }
 /**
  * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text state is no
  * longer what the IME has and that it needs to be updated.
  */
 void restartInput() {
   if (DEBUG) Log.w(TAG, "restartInput");
   getInputMethodManagerWrapper().restartInput(mInternalView);
   mNumNestedBatchEdits = 0;
   mPendingAccent = 0;
 }