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; }
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); }
/** * * 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(); } } }
/** * 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; }