Loading core/java/android/companion/virtual/computercontrol/IComputerControlSession.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -55,7 +55,8 @@ interface IComputerControlSession { int width, int height, in Surface surface); /** * Inserts text into the current active input connection (if available). * Inserts text into the current active input connection. If there is no active input * connection, this method is no-op. * * @param text to be inserted * @param replaceExisting whether the existing text in the input field should be replaced. If Loading core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +5 −3 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputMethodInfoSafeList; import com.android.internal.inputmethod.SoftInputShowHideReason; Loading Loading @@ -298,6 +299,7 @@ final class IInputMethodManagerGlobalInvoker { @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo, @Nullable IRemoteInputConnection remoteInputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, @Nullable IRemoteComputerControlInputConnection remoteComputerControlInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ResultReceiver imeBackCallbackReceiver, boolean imeRequestedVisible) { final IInputMethodManager service = getService(); Loading @@ -307,9 +309,9 @@ final class IInputMethodManagerGlobalInvoker { try { service.startInputOrWindowGainedFocus(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection, remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId, imeBackCallbackReceiver, imeRequestedVisible, advanceAngGetStartInputSequenceNumber()); remoteAccessibilityInputConnection, remoteComputerControlInputConnection, unverifiedTargetSdkVersion, userId, imeBackCallbackReceiver, imeRequestedVisible, advanceAngGetStartInputSequenceNumber()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading core/java/android/view/inputmethod/InputMethodManager.java +10 −4 Original line number Diff line number Diff line Loading @@ -116,6 +116,7 @@ import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IInputMethodSession; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; Loading Loading @@ -940,8 +941,7 @@ public final class InputMethodManager { StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, viewForWindowFocus.getWindowToken(), startInputFlags, softInputMode, windowFlags, null, null, null, null, null, null, null, mCurRootView.mContext.getApplicationInfo().targetSdkVersion, UserHandle.myUserId(), mImeBackCallbackProxy.getResultReceiver(), imeRequestedVisible); Loading Loading @@ -3635,13 +3635,19 @@ public final class InputMethodManager { ? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus"); final IRemoteAccessibilityInputConnection accessibilityInputConnection = servedInputConnection == null ? null : servedInputConnection.asIRemoteAccessibilityInputConnection(); final IRemoteComputerControlInputConnection computerControlInputConnection = (!android.companion.virtualdevice.flags.Flags.computerControlTyping() || servedInputConnection == null) ? null : servedInputConnection.asIRemoteComputerControlInputConnection(); // async result delivered via MSG_START_INPUT_RESULT. final int startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, editorInfo, servedInputConnection, servedInputConnection == null ? null : servedInputConnection.asIRemoteAccessibilityInputConnection(), accessibilityInputConnection, computerControlInputConnection, view.getContext().getApplicationInfo().targetSdkVersion, targetUserId, mImeBackCallbackProxy.getResultReceiver(), imeRequestedVisible); Loading core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +65 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import android.view.ViewRootImpl; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputConnectionCommandHeader; Loading Loading @@ -1436,6 +1437,70 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return mAccessibilityInputConnection; } private final IRemoteComputerControlInputConnection mComputerControlInputConnection = new IRemoteComputerControlInputConnection.Stub() { @Dispatching(cancellable = true) @Override public void commitText(@NonNull InputConnectionCommandHeader header, @NonNull CharSequence text, int newCursorPosition) { dispatchWithTracing("commitTextFromComputerControl", () -> { if (!checkSessionId(header)) { return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { Log.w(TAG, "commitText on inactive InputConnection"); return; } ic.commitText(text, newCursorPosition); }); } @Dispatching(cancellable = true) @Override public void replaceText(@NonNull InputConnectionCommandHeader header, int start, int end, @NonNull CharSequence text, int newCursorPosition) { dispatchWithTracing("replaceTextFromComputerControl", () -> { if (!checkSessionId(header)) { return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { Log.w(TAG, "replaceText on inactive InputConnection"); return; } ic.replaceText(start, end, text, newCursorPosition, null /* textAttribute */); }); } @Dispatching(cancellable = true) @Override public void sendKeyEvent(@NonNull InputConnectionCommandHeader header, @NonNull KeyEvent event) { dispatchWithTracing("sendKeyEventFromComputerControl", () -> { if (!checkSessionId(header)) { return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { Log.w(TAG, "sendKeyEvent on inactive InputConnection"); return; } ic.sendKeyEvent(event); }); } }; /** * @return {@link IRemoteComputerControlInputConnection} associated with this object. */ @NonNull public IRemoteComputerControlInputConnection asIRemoteComputerControlInputConnection() { return mComputerControlInputConnection; } private void dispatch(@NonNull Runnable runnable) { // If we are calling this from the target thread, then we can call right through. // Otherwise, we need to send the message to the target thread. Loading core/java/com/android/internal/inputmethod/IRemoteComputerControlInputConnection.aidl 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.inputmethod; import android.view.KeyEvent; import android.view.inputmethod.TextAttribute; import com.android.internal.inputmethod.InputConnectionCommandHeader; /** * Interface from computer control session to the application, allowing it to perform edits on the * current input field and other interactions with the application. These methods call into * corresponding {@link InputConnection} methods. */ oneway interface IRemoteComputerControlInputConnection { /** * Commit text to the text box and set the new cursor position. * * <p>This method commits the contents of the currently composing text, and then moves the * cursor according to {@code newCursorPosition}. If there is no composing text when this * method is called, the new text is inserted at the cursor position, removing text inside the * selection if any. * * @param text The text to commit. This may include styles. * @param newCursorPosition The new cursor position around the text. If > 0, this is relative to * the end of the text - 1; if <= 0, this is relative to the start * of the text. So a value of 1 will always advance the cursor to the * position after the full text being inserted. * * @see InputConnection#commitText(CharSequence, int) */ void commitText(in InputConnectionCommandHeader header, in CharSequence text, int newCursorPosition); /** * Replace the specific range in the editor with suggested text. * * <p>This method finishes whatever composing text is currently active and leaves the text * as-it, replaces the specific range of text with the passed CharSequence, and then moves the * cursor according to {@code newCursorPosition}. * * @param start the character index where the replacement should start. * @param end the character index where the replacement should end. * @param newCursorPosition The new cursor position around the text. If > 0, this is relative to * the end of the text - 1; if <= 0, this is relative to the start * of the text. So a value of 1 will always advance the cursor to the * position after the full text being inserted. * @param text the text to replace. This may include styles. * * @see InputConnection#replaceText(int, int, CharSequence, int, TextAttribute) */ void replaceText(in InputConnectionCommandHeader header, int start, int end, in CharSequence text, int newCursorPosition); /** * Send a key event to the process that is currently attached through this input connection. * The event will be dispatched like a normal key event, to the currently focused view; this * generally is the view that is providing this {@link InputConnection}. * * @param event The key event. * * @see KeyEvent * @see InputConnection#sendKeyEvent(KeyEvent) */ void sendKeyEvent(in InputConnectionCommandHeader header, in KeyEvent event); } Loading
core/java/android/companion/virtual/computercontrol/IComputerControlSession.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -55,7 +55,8 @@ interface IComputerControlSession { int width, int height, in Surface surface); /** * Inserts text into the current active input connection (if available). * Inserts text into the current active input connection. If there is no active input * connection, this method is no-op. * * @param text to be inserted * @param replaceExisting whether the existing text in the input field should be replaced. If Loading
core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +5 −3 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputMethodInfoSafeList; import com.android.internal.inputmethod.SoftInputShowHideReason; Loading Loading @@ -298,6 +299,7 @@ final class IInputMethodManagerGlobalInvoker { @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo, @Nullable IRemoteInputConnection remoteInputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, @Nullable IRemoteComputerControlInputConnection remoteComputerControlInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ResultReceiver imeBackCallbackReceiver, boolean imeRequestedVisible) { final IInputMethodManager service = getService(); Loading @@ -307,9 +309,9 @@ final class IInputMethodManagerGlobalInvoker { try { service.startInputOrWindowGainedFocus(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection, remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId, imeBackCallbackReceiver, imeRequestedVisible, advanceAngGetStartInputSequenceNumber()); remoteAccessibilityInputConnection, remoteComputerControlInputConnection, unverifiedTargetSdkVersion, userId, imeBackCallbackReceiver, imeRequestedVisible, advanceAngGetStartInputSequenceNumber()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading
core/java/android/view/inputmethod/InputMethodManager.java +10 −4 Original line number Diff line number Diff line Loading @@ -116,6 +116,7 @@ import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IInputMethodSession; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; Loading Loading @@ -940,8 +941,7 @@ public final class InputMethodManager { StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, viewForWindowFocus.getWindowToken(), startInputFlags, softInputMode, windowFlags, null, null, null, null, null, null, null, mCurRootView.mContext.getApplicationInfo().targetSdkVersion, UserHandle.myUserId(), mImeBackCallbackProxy.getResultReceiver(), imeRequestedVisible); Loading Loading @@ -3635,13 +3635,19 @@ public final class InputMethodManager { ? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus"); final IRemoteAccessibilityInputConnection accessibilityInputConnection = servedInputConnection == null ? null : servedInputConnection.asIRemoteAccessibilityInputConnection(); final IRemoteComputerControlInputConnection computerControlInputConnection = (!android.companion.virtualdevice.flags.Flags.computerControlTyping() || servedInputConnection == null) ? null : servedInputConnection.asIRemoteComputerControlInputConnection(); // async result delivered via MSG_START_INPUT_RESULT. final int startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, editorInfo, servedInputConnection, servedInputConnection == null ? null : servedInputConnection.asIRemoteAccessibilityInputConnection(), accessibilityInputConnection, computerControlInputConnection, view.getContext().getApplicationInfo().targetSdkVersion, targetUserId, mImeBackCallbackProxy.getResultReceiver(), imeRequestedVisible); Loading
core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +65 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import android.view.ViewRootImpl; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputConnectionCommandHeader; Loading Loading @@ -1436,6 +1437,70 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return mAccessibilityInputConnection; } private final IRemoteComputerControlInputConnection mComputerControlInputConnection = new IRemoteComputerControlInputConnection.Stub() { @Dispatching(cancellable = true) @Override public void commitText(@NonNull InputConnectionCommandHeader header, @NonNull CharSequence text, int newCursorPosition) { dispatchWithTracing("commitTextFromComputerControl", () -> { if (!checkSessionId(header)) { return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { Log.w(TAG, "commitText on inactive InputConnection"); return; } ic.commitText(text, newCursorPosition); }); } @Dispatching(cancellable = true) @Override public void replaceText(@NonNull InputConnectionCommandHeader header, int start, int end, @NonNull CharSequence text, int newCursorPosition) { dispatchWithTracing("replaceTextFromComputerControl", () -> { if (!checkSessionId(header)) { return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { Log.w(TAG, "replaceText on inactive InputConnection"); return; } ic.replaceText(start, end, text, newCursorPosition, null /* textAttribute */); }); } @Dispatching(cancellable = true) @Override public void sendKeyEvent(@NonNull InputConnectionCommandHeader header, @NonNull KeyEvent event) { dispatchWithTracing("sendKeyEventFromComputerControl", () -> { if (!checkSessionId(header)) { return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { Log.w(TAG, "sendKeyEvent on inactive InputConnection"); return; } ic.sendKeyEvent(event); }); } }; /** * @return {@link IRemoteComputerControlInputConnection} associated with this object. */ @NonNull public IRemoteComputerControlInputConnection asIRemoteComputerControlInputConnection() { return mComputerControlInputConnection; } private void dispatch(@NonNull Runnable runnable) { // If we are calling this from the target thread, then we can call right through. // Otherwise, we need to send the message to the target thread. Loading
core/java/com/android/internal/inputmethod/IRemoteComputerControlInputConnection.aidl 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.inputmethod; import android.view.KeyEvent; import android.view.inputmethod.TextAttribute; import com.android.internal.inputmethod.InputConnectionCommandHeader; /** * Interface from computer control session to the application, allowing it to perform edits on the * current input field and other interactions with the application. These methods call into * corresponding {@link InputConnection} methods. */ oneway interface IRemoteComputerControlInputConnection { /** * Commit text to the text box and set the new cursor position. * * <p>This method commits the contents of the currently composing text, and then moves the * cursor according to {@code newCursorPosition}. If there is no composing text when this * method is called, the new text is inserted at the cursor position, removing text inside the * selection if any. * * @param text The text to commit. This may include styles. * @param newCursorPosition The new cursor position around the text. If > 0, this is relative to * the end of the text - 1; if <= 0, this is relative to the start * of the text. So a value of 1 will always advance the cursor to the * position after the full text being inserted. * * @see InputConnection#commitText(CharSequence, int) */ void commitText(in InputConnectionCommandHeader header, in CharSequence text, int newCursorPosition); /** * Replace the specific range in the editor with suggested text. * * <p>This method finishes whatever composing text is currently active and leaves the text * as-it, replaces the specific range of text with the passed CharSequence, and then moves the * cursor according to {@code newCursorPosition}. * * @param start the character index where the replacement should start. * @param end the character index where the replacement should end. * @param newCursorPosition The new cursor position around the text. If > 0, this is relative to * the end of the text - 1; if <= 0, this is relative to the start * of the text. So a value of 1 will always advance the cursor to the * position after the full text being inserted. * @param text the text to replace. This may include styles. * * @see InputConnection#replaceText(int, int, CharSequence, int, TextAttribute) */ void replaceText(in InputConnectionCommandHeader header, int start, int end, in CharSequence text, int newCursorPosition); /** * Send a key event to the process that is currently attached through this input connection. * The event will be dispatched like a normal key event, to the currently focused view; this * generally is the view that is providing this {@link InputConnection}. * * @param event The key event. * * @see KeyEvent * @see InputConnection#sendKeyEvent(KeyEvent) */ void sendKeyEvent(in InputConnectionCommandHeader header, in KeyEvent event); }