Merge "Address API feedback for ApduServiceInfo" into main
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 926e297..e6a2c07 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -237,7 +237,6 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.MemInfoDumpProto;
-import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.AppSpecializationHooks;
@@ -3762,11 +3761,7 @@
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
activityToken, list);
- if (Flags.bundleClientTransactionFlag()) {
- clientTransaction.addTransactionItem(activityResultItem);
- } else {
- clientTransaction.addCallback(activityResultItem);
- }
+ clientTransaction.addTransactionItem(activityResultItem);
try {
mAppThread.scheduleTransaction(clientTransaction);
} catch (RemoteException e) {
@@ -4553,11 +4548,7 @@
final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
/* dontReport */ false, /* autoEnteringPip */ false);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(pauseActivityItem);
- } else {
- transaction.setLifecycleStateRequest(pauseActivityItem);
- }
+ transaction.addTransactionItem(pauseActivityItem);
executeTransaction(transaction);
}
@@ -4565,11 +4556,7 @@
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(resumeActivityItem);
- } else {
- transaction.setLifecycleStateRequest(resumeActivityItem);
- }
+ transaction.addTransactionItem(resumeActivityItem);
executeTransaction(transaction);
}
@@ -6189,13 +6176,8 @@
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
// Schedule the transaction.
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(activityRelaunchItem);
- transaction.addTransactionItem(lifecycleRequest);
- } else {
- transaction.addCallback(activityRelaunchItem);
- transaction.setLifecycleStateRequest(lifecycleRequest);
- }
+ transaction.addTransactionItem(activityRelaunchItem);
+ transaction.addTransactionItem(lifecycleRequest);
executeTransaction(transaction);
}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 612d433..79696e0 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -29,6 +29,7 @@
import android.os.RemoteException;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,12 +86,15 @@
* @param item A single message that can contain a client activity/window request/callback.
*/
public void addTransactionItem(@NonNull ClientTransactionItem item) {
- if (mTransactionItems == null) {
- mTransactionItems = new ArrayList<>();
+ if (Flags.bundleClientTransactionFlag()) {
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
+ }
+ mTransactionItems.add(item);
}
- mTransactionItems.add(item);
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
+ // Populate even if mTransactionItems is set to support the UnsupportedAppUsage.
if (item.isActivityLifecycleItem()) {
setLifecycleStateRequest((ActivityLifecycleItem) item);
} else {
@@ -114,7 +118,7 @@
*/
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@Deprecated
- public void addCallback(@NonNull ClientTransactionItem activityCallback) {
+ private void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
}
@@ -169,7 +173,7 @@
*/
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@Deprecated
- public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
+ private void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
if (mLifecycleStateRequest != null) {
return;
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 9f97f6f..1a8136e 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -23,7 +23,6 @@
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
-import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@
* window configuration.
*/
public void onDisplayChanged(int displayId) {
- if (!isBundleClientTransactionFlagEnabled()) {
+ if (!bundleClientTransactionFlag()) {
return;
}
if (ActivityThread.isSystem()) {
@@ -76,10 +75,4 @@
}
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
-
- /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
- public boolean isBundleClientTransactionFlagEnabled() {
- // Can't read flag from isolated process.
- return !Process.isIsolated() && bundleClientTransactionFlag();
- }
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 406e00a..fa73c99 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -40,7 +40,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.IBinder;
-import android.os.Process;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -218,8 +217,6 @@
final boolean shouldTrackConfigUpdatedContext =
// No configuration change for local transaction.
!mTransactionHandler.isExecutingLocalTransaction()
- // Can't read flag from isolated process.
- && !Process.isIsolated()
&& bundleClientTransactionFlag();
final Context configUpdatedContext = shouldTrackConfigUpdatedContext
? item.getContextToUpdate(mTransactionHandler)
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index 1cc910c..e47a48d 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -48,14 +48,13 @@
private static final int GRAVITY_RIGHT = 0x2;
private static final int GRAVITY_TOP = 0x4;
private static final int GRAVITY_BOTTOM = 0x8;
- private static final int GRAVITY_CENTER =
- GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_TOP | GRAVITY_BOTTOM;
- private static final int GRAVITY_CENTER_HORIZONTAL = GRAVITY_LEFT | GRAVITY_RIGHT;
+ private static final int TEXT_PADDING_IN_DP = 1;
private static final int KEY_PADDING_IN_DP = 3;
private static final int KEYBOARD_PADDING_IN_DP = 10;
private static final int KEY_RADIUS_IN_DP = 5;
private static final int KEYBOARD_RADIUS_IN_DP = 10;
- private static final int GLYPH_TEXT_SIZE_IN_SP = 10;
+ private static final int MIN_GLYPH_TEXT_SIZE_IN_SP = 10;
+ private static final int MAX_GLYPH_TEXT_SIZE_IN_SP = 20;
private final List<KeyDrawable> mKeyDrawables = new ArrayList<>();
@@ -107,6 +106,8 @@
}
int rowCount = keys.length;
float keyHeight = (float) (height - rowCount * 2 * keyPadding) / rowCount;
+ // Based on key height calculate the max text size that can fit for typing keys
+ mResourceProvider.calculateBestTextSizeForKey(keyHeight);
float isoEnterKeyLeft = 0;
float isoEnterKeyTop = 0;
float isoEnterWidthUnit = 0;
@@ -136,16 +137,19 @@
}
if (PhysicalKeyLayout.isSpecialKey(row[j])) {
mKeyDrawables.add(new TypingKey(null, keyRect, keyRadius,
+ mResourceProvider.getTextPadding(),
mResourceProvider.getSpecialKeyPaint(),
mResourceProvider.getSpecialKeyPaint(),
mResourceProvider.getSpecialKeyPaint()));
} else if (PhysicalKeyLayout.isKeyPositionUnsure(row[j])) {
mKeyDrawables.add(new UnsureTypingKey(row[j].glyph(), keyRect,
- keyRadius, mResourceProvider.getTypingKeyPaint(),
+ keyRadius, mResourceProvider.getTextPadding(),
+ mResourceProvider.getTypingKeyPaint(),
mResourceProvider.getPrimaryGlyphPaint(),
mResourceProvider.getSecondaryGlyphPaint()));
} else {
mKeyDrawables.add(new TypingKey(row[j].glyph(), keyRect, keyRadius,
+ mResourceProvider.getTextPadding(),
mResourceProvider.getTypingKeyPaint(),
mResourceProvider.getPrimaryGlyphPaint(),
mResourceProvider.getSecondaryGlyphPaint()));
@@ -192,15 +196,18 @@
private final RectF mKeyRect;
private final float mKeyRadius;
+ private final float mTextPadding;
private final Paint mKeyPaint;
private final Paint mBaseTextPaint;
private final Paint mModifierTextPaint;
private final List<GlyphDrawable> mGlyphDrawables = new ArrayList<>();
private TypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, RectF keyRect,
- float keyRadius, Paint keyPaint, Paint baseTextPaint, Paint modifierTextPaint) {
+ float keyRadius, float textPadding, Paint keyPaint, Paint baseTextPaint,
+ Paint modifierTextPaint) {
mKeyRect = keyRect;
mKeyRadius = keyRadius;
+ mTextPadding = textPadding;
mKeyPaint = keyPaint;
mBaseTextPaint = baseTextPaint;
mModifierTextPaint = modifierTextPaint;
@@ -219,20 +226,17 @@
if (!glyphData.hasBaseText()) {
return;
}
- boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
- mBaseTextPaint));
+ GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
if (glyphData.hasValidShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
- mModifierTextPaint));
+ GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
}
if (glyphData.hasValidAltGrText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
}
- if (glyphData.hasValidAltShiftText()) {
+ if (glyphData.hasValidAltGrShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
}
@@ -246,15 +250,19 @@
float centerY = keyHeight / 2;
if ((glyph.gravity & GRAVITY_LEFT) != 0) {
centerX -= keyWidth / 4;
+ centerX += mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_RIGHT) != 0) {
centerX += keyWidth / 4;
+ centerX -= mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_TOP) != 0) {
centerY -= keyHeight / 4;
+ centerY += mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_BOTTOM) != 0) {
centerY += keyHeight / 4;
+ centerY -= mTextPadding / 2;
}
Rect textBounds = new Rect();
glyph.paint.getTextBounds(glyph.text, 0, glyph.text.length(), textBounds);
@@ -285,9 +293,9 @@
private static class UnsureTypingKey extends TypingKey {
private UnsureTypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData,
- RectF keyRect, float keyRadius, Paint keyPaint, Paint baseTextPaint,
- Paint modifierTextPaint) {
- super(glyphData, keyRect, keyRadius, createGreyedOutPaint(keyPaint),
+ RectF keyRect, float keyRadius, float textPadding, Paint keyPaint,
+ Paint baseTextPaint, Paint modifierTextPaint) {
+ super(glyphData, keyRect, keyRadius, textPadding, createGreyedOutPaint(keyPaint),
createGreyedOutPaint(baseTextPaint), createGreyedOutPaint(modifierTextPaint));
}
}
@@ -402,8 +410,11 @@
private final Paint mSecondaryGlyphPaint;
private final int mKeyPadding;
private final int mKeyboardPadding;
+ private final float mTextPadding;
private final float mKeyRadius;
private final float mBackgroundRadius;
+ private final float mSpToPxMultiplier;
+ private final Paint.FontMetrics mFontMetrics;
private ResourceProvider(Context context) {
mKeyPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
@@ -414,8 +425,10 @@
KEY_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
mBackgroundRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
KEYBOARD_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
- int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
- GLYPH_TEXT_SIZE_IN_SP, context.getResources().getDisplayMetrics());
+ mSpToPxMultiplier = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
+ context.getResources().getDisplayMetrics());
+ mTextPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ TEXT_PADDING_IN_DP, context.getResources().getDisplayMetrics());
boolean isDark = (context.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
int typingKeyColor = context.getColor(
@@ -430,15 +443,37 @@
int backgroundColor = context.getColor(
isDark ? android.R.color.system_surface_container_dark
: android.R.color.system_surface_container_light);
- mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor, textSize,
+ mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor,
+ MIN_GLYPH_TEXT_SIZE_IN_SP * mSpToPxMultiplier,
Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
- mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor, textSize,
+ mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor,
+ MIN_GLYPH_TEXT_SIZE_IN_SP * mSpToPxMultiplier,
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
+ mFontMetrics = mPrimaryGlyphPaint.getFontMetrics();
mTypingKeyPaint = createFillPaint(typingKeyColor);
mSpecialKeyPaint = createFillPaint(specialKeyColor);
mBackgroundPaint = createFillPaint(backgroundColor);
}
+ private void calculateBestTextSizeForKey(float keyHeight) {
+ int textSize = (int) (mSpToPxMultiplier * MIN_GLYPH_TEXT_SIZE_IN_SP) + 1;
+ while (textSize < mSpToPxMultiplier * MAX_GLYPH_TEXT_SIZE_IN_SP) {
+ updateTextSize(textSize);
+ if (mFontMetrics.bottom - mFontMetrics.top + 3 * mTextPadding > keyHeight / 2) {
+ textSize--;
+ break;
+ }
+ textSize++;
+ }
+ updateTextSize(textSize);
+ }
+
+ private void updateTextSize(float textSize) {
+ mPrimaryGlyphPaint.setTextSize(textSize);
+ mSecondaryGlyphPaint.setTextSize(textSize);
+ mPrimaryGlyphPaint.getFontMetrics(mFontMetrics);
+ }
+
private Paint getBackgroundPaint() {
return mBackgroundPaint;
}
@@ -467,6 +502,10 @@
return mKeyboardPadding;
}
+ private float getTextPadding() {
+ return mTextPadding;
+ }
+
private float getKeyRadius() {
return mKeyRadius;
}
@@ -476,7 +515,8 @@
}
}
- private static Paint createTextPaint(@ColorInt int textColor, int textSize, Typeface typeface) {
+ private static Paint createTextPaint(@ColorInt int textColor, float textSize,
+ Typeface typeface) {
Paint paint = new Paint();
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 844e02f..cff444f 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -336,11 +336,13 @@
return "";
}
int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
+ if (utf8Char == 0) {
+ return "";
+ }
if (Character.isValidCodePoint(utf8Char)) {
return String.valueOf(Character.toChars(utf8Char));
- } else {
- return String.valueOf(kcm.getDisplayLabel(keyCode));
}
+ return "□";
}
private static LayoutKey getKey(int keyCode, float keyWeight) {
@@ -434,10 +436,11 @@
}
public boolean hasValidAltGrText() {
- return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
+ return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText)
+ && !TextUtils.equals(mShiftText, mAltGrText);
}
- public boolean hasValidAltShiftText() {
+ public boolean hasValidAltGrShiftText() {
return !TextUtils.isEmpty(mAltGrShiftText)
&& !TextUtils.equals(mBaseText, mAltGrShiftText)
&& !TextUtils.equals(mAltGrText, mAltGrShiftText)
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index dc5e0e5..88ca2a4 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -66,6 +66,7 @@
@Nullable
private static volatile IImeTracker sTrackerServiceCache = null;
+ private static int sCurStartInputSeq = 0;
/**
* @return {@code true} if {@link IInputMethodManager} is available.
@@ -327,6 +328,7 @@
}
}
+ // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
@AnyThread
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@@ -353,6 +355,41 @@
}
}
+ /**
+ * Returns a sequence number for startInput.
+ */
+ @AnyThread
+ @NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+ static int startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason,
+ @NonNull IInputMethodClient client, @Nullable IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo,
+ @Nullable IRemoteInputConnection remoteInputConnection,
+ @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return -1;
+ }
+ try {
+ service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
+ remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
+ imeDispatcher, advanceAngGetStartInputSequenceNumber());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return sCurStartInputSeq;
+ }
+
+ private static int advanceAngGetStartInputSequenceNumber() {
+ return ++sCurStartInputSeq;
+ }
+
+
@AnyThread
static void showInputMethodPickerFromClient(@NonNull IInputMethodClient client,
int auxiliarySubtypeMode) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f4b09df..72125ba 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -321,6 +321,22 @@
};
/**
+ * A runnable that reports {@link InputConnection} opened event for calls to
+ * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}.
+ */
+ private abstract static class ReportInputConnectionOpenedRunner implements Runnable {
+ /**
+ * Sequence number to track startInput requests to
+ * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}
+ */
+ int mSequenceNum;
+ ReportInputConnectionOpenedRunner(int sequenceNum) {
+ this.mSequenceNum = sequenceNum;
+ }
+ }
+ private ReportInputConnectionOpenedRunner mReportInputConnectionOpenedRunner;
+
+ /**
* Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
* or indirectly relied on {@link #sInstance} via reflection or something like that.
*
@@ -691,6 +707,7 @@
private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
private static final int MSG_SET_INTERACTIVE = 13;
private static final int MSG_ON_SHOW_REQUESTED = 31;
+ private static final int MSG_START_INPUT_RESULT = 40;
/**
* Calling this will invalidate Local stylus handwriting availability Cache which
@@ -1045,7 +1062,7 @@
return;
}
case MSG_BIND: {
- final InputBindResult res = (InputBindResult)msg.obj;
+ final InputBindResult res = (InputBindResult) msg.obj;
if (DEBUG) {
Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
}
@@ -1071,6 +1088,60 @@
startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
return;
}
+
+ case MSG_START_INPUT_RESULT: {
+ final InputBindResult res = (InputBindResult) msg.obj;
+ final int startInputSeq = msg.arg1;
+ if (res == null) {
+ // IMMS logs .wtf already.
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+ synchronized (mH) {
+ if (res.id != null) {
+ updateInputChannelLocked(res.channel);
+ mCurMethod = res.method; // for @UnsupportedAppUsage
+ mCurBindState = new BindState(res);
+ mAccessibilityInputMethodSession.clear();
+ if (res.accessibilitySessions != null) {
+ for (int i = 0; i < res.accessibilitySessions.size(); i++) {
+ IAccessibilityInputMethodSessionInvoker wrapper =
+ IAccessibilityInputMethodSessionInvoker.createOrNull(
+ res.accessibilitySessions.valueAt(i));
+ if (wrapper != null) {
+ mAccessibilityInputMethodSession.append(
+ res.accessibilitySessions.keyAt(i), wrapper);
+ }
+ }
+ }
+ mCurId = res.id; // for @UnsupportedAppUsage
+ } else if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ switch (res.result) {
+ case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+ mRestartOnNextWindowFocus = true;
+ mServedView = null;
+ break;
+ }
+ if (mCompletions != null) {
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.displayCompletions(mCompletions);
+ }
+ }
+
+ if (res != null
+ && res.method != null
+ && mServedView != null
+ && mReportInputConnectionOpenedRunner != null
+ && mReportInputConnectionOpenedRunner.mSequenceNum
+ == startInputSeq) {
+ mReportInputConnectionOpenedRunner.run();
+ }
+ mReportInputConnectionOpenedRunner = null;
+ }
+ return;
+ }
case MSG_UNBIND: {
final int sequence = msg.arg1;
@UnbindReason
@@ -1322,6 +1393,12 @@
}
@Override
+ public void onStartInputResult(InputBindResult res, int startInputSeq) {
+ mH.obtainMessage(MSG_START_INPUT_RESULT, startInputSeq, -1 /* unused */, res)
+ .sendToTarget();
+ }
+
+ @Override
public void onBindAccessibilityService(InputBindResult res, int id) {
mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget();
}
@@ -2010,6 +2087,7 @@
mServedConnecting = false;
clearConnectionLocked();
}
+ mReportInputConnectionOpenedRunner = null;
// Clear the back callbacks held by the ime dispatcher to avoid memory leaks.
mImeDispatcher.clear();
}
@@ -3080,14 +3158,52 @@
final int targetUserId = editorInfo.targetInputMethodUser != null
? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");
- res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
- startInputReason, mClient, windowGainingFocus, startInputFlags,
- softInputMode, windowFlags, editorInfo, servedInputConnection,
- servedInputConnection == null ? null
- : servedInputConnection.asIRemoteAccessibilityInputConnection(),
- view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher);
+
+ int startInputSeq = -1;
+ if (Flags.useZeroJankProxy()) {
+ // async result delivered via MSG_START_INPUT_RESULT.
+ startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, editorInfo, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
+ mImeDispatcher);
+ } else {
+ res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, editorInfo, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
+ mImeDispatcher);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (Flags.useZeroJankProxy()) {
+ // Create a runnable for delayed notification to the app that the InputConnection is
+ // initialized and ready for use.
+ if (ic != null) {
+ final int seqId = startInputSeq;
+ mReportInputConnectionOpenedRunner =
+ new ReportInputConnectionOpenedRunner(startInputSeq) {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.v(TAG, "Calling View.onInputConnectionOpened: view= "
+ + view
+ + ", ic=" + ic + ", editorInfo=" + editorInfo
+ + ", handler="
+ + icHandler + ", startInputSeq=" + seqId);
+ }
+ reportInputConnectionOpened(ic, editorInfo, icHandler, view);
+ }
+ };
+ } else {
+ mReportInputConnectionOpenedRunner = null;
+ }
+ return true;
+ }
+
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
@@ -3118,6 +3234,7 @@
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
+
switch (res.result) {
case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
mRestartOnNextWindowFocus = true;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
index 9251d2d..babd9a0 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
@@ -24,6 +24,7 @@
*/
oneway interface IInputMethodClient {
void onBindMethod(in InputBindResult res);
+ void onStartInputResult(in InputBindResult res, int startInputSeq);
void onBindAccessibilityService(in InputBindResult res, int id);
void onUnbindMethod(int sequence, int unbindReason);
void onUnbindAccessibilityService(int sequence, int id);
diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java
index b6eca07..243b103 100644
--- a/core/java/com/android/internal/inputmethod/InputBindResult.java
+++ b/core/java/com/android/internal/inputmethod/InputBindResult.java
@@ -271,6 +271,7 @@
public String toString() {
return "InputBindResult{result=" + getResultString() + " method=" + method + " id=" + id
+ " sequence=" + sequence
+ + " result=" + result
+ " isInputMethodSuppressingSpellChecker=" + isInputMethodSuppressingSpellChecker
+ "}";
}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 48c455a..3662d69 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -120,7 +120,7 @@
public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
- public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
+ // 87 is reserved - previously assigned to deprecated CUJ_LAUNCHER_SEARCH_QSB_OPEN.
public static final int CUJ_BACK_PANEL_ARROW = 88;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK = 89;
public static final int CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH = 90;
@@ -209,7 +209,6 @@
CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
CUJ_PREDICTIVE_BACK_CROSS_TASK,
CUJ_PREDICTIVE_BACK_HOME,
- CUJ_LAUNCHER_SEARCH_QSB_OPEN,
CUJ_BACK_PANEL_ARROW,
CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK,
CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH,
@@ -304,7 +303,6 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_WEB_SEARCH;
@@ -480,8 +478,6 @@
return "PREDICTIVE_BACK_CROSS_TASK";
case CUJ_PREDICTIVE_BACK_HOME:
return "PREDICTIVE_BACK_HOME";
- case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
- return "LAUNCHER_SEARCH_QSB_OPEN";
case CUJ_BACK_PANEL_ARROW:
return "BACK_PANEL_ARROW";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK:
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index e95127b..b90f8bf 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -69,6 +69,8 @@
boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
in @nullable ImeTracker.Token statsToken, int flags,
in @nullable ResultReceiver resultReceiver, int reason);
+
+ // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
@@ -85,6 +87,21 @@
int unverifiedTargetSdkVersion, int userId,
in ImeOnBackInvokedDispatcher imeDispatcher);
+ // If windowToken is null, this just does startInput(). Otherwise this reports that a window
+ // has gained focus, and if 'editorInfo' is non-null then also does startInput.
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ void startInputOrWindowGainedFocusAsync(
+ /* @StartInputReason */ int startInputReason,
+ in IInputMethodClient client, in @nullable IBinder windowToken,
+ /* @StartInputFlags */ int startInputFlags,
+ /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
+ /* @android.view.WindowManager.LayoutParams.Flags */ int windowFlags,
+ in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
+ in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, int userId,
+ in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
@@ -156,6 +173,7 @@
in String delegatePackageName,
in String delegatorPackageName);
+ // TODO(b/293640003): introduce a new API method to provide async way to return boolean.
/** Accepts and starts a stylus handwriting session for the delegate view **/
boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, in int userId,
in String delegatePackageName, in String delegatorPackageName, int flags);
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 48ef7e6..ebf4cca 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -43,7 +43,6 @@
import android.app.PictureInPictureUiState;
import android.app.ResourcesManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
-import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
@@ -75,7 +74,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.ReferrerIntent;
-import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -230,7 +228,7 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -247,7 +245,7 @@
newConfig.seq++;
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), newConfig));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -448,16 +446,16 @@
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigLandscape));
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
processConfigPortrait, DEVICE_ID_INVALID));
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigPortrait));
appThread.scheduleTransaction(transaction);
@@ -847,8 +845,8 @@
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, callbackItem);
- addClientTransactionItem(transaction, resumeStateRequest);
+ transaction.addTransactionItem(callbackItem);
+ transaction.addTransactionItem(resumeStateRequest);
return transaction;
}
@@ -860,7 +858,7 @@
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, resumeStateRequest);
+ transaction.addTransactionItem(resumeStateRequest);
return transaction;
}
@@ -871,7 +869,7 @@
activity.getActivityToken(), 0 /* configChanges */);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, stopStateRequest);
+ transaction.addTransactionItem(stopStateRequest);
return transaction;
}
@@ -883,7 +881,7 @@
activity.getActivityToken(), config);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, item);
+ transaction.addTransactionItem(item);
return transaction;
}
@@ -895,7 +893,7 @@
resume);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, item);
+ transaction.addTransactionItem(item);
return transaction;
}
@@ -910,17 +908,6 @@
return ClientTransaction.obtain(activityThread.getApplicationThread());
}
- private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
- @NonNull ClientTransactionItem item) {
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(item);
- } else if (item.isActivityLifecycleItem()) {
- transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
- } else {
- transaction.addCallback(item);
- }
- }
-
// Test activity
public static class TestActivity extends Activity {
static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 95d5049..213fd7b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -16,10 +16,13 @@
package android.app.servertransaction;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManager;
@@ -28,12 +31,14 @@
import android.os.Handler;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -49,6 +54,10 @@
@SmallTest
@Presubmit
public class ClientTransactionListenerControllerTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private IDisplayManager mIDisplayManager;
@Mock
@@ -60,12 +69,12 @@
@Before
public void setup() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
MockitoAnnotations.initMocks(this);
mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
mHandler = getInstrumentation().getContext().getMainThreadHandler();
- mController = spy(ClientTransactionListenerController.createInstanceForTesting(
- mDisplayManager));
- doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
+ mController = ClientTransactionListenerController.createInstanceForTesting(mDisplayManager);
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index d10cf16..5272416 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -16,16 +16,22 @@
package android.app.servertransaction;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ClientTransactionHandler;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,31 +49,28 @@
@Presubmit
public class ClientTransactionTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Test
public void testPreExecute() {
- final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
- final ClientTransactionHandler clientTransactionHandler =
- mock(ClientTransactionHandler.class);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
- transaction.setLifecycleStateRequest(stateRequest);
-
- transaction.preExecute(clientTransactionHandler);
-
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ testPreExecuteInner();
}
@Test
- public void testPreExecuteTransactionItems() {
+ public void testPreExecute_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testPreExecuteInner();
+ }
+
+ private void testPreExecuteInner() {
final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ doReturn(true).when(stateRequest).isActivityLifecycleItem();
final ClientTransactionHandler clientTransactionHandler =
mock(ClientTransactionHandler.class);
@@ -78,8 +81,8 @@
transaction.preExecute(clientTransactionHandler);
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ verify(callback1).preExecute(clientTransactionHandler);
+ verify(callback2).preExecute(clientTransactionHandler);
+ verify(stateRequest).preExecute(clientTransactionHandler);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 2315a58..adb6f2a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -25,6 +25,9 @@
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -51,12 +54,14 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -83,6 +88,9 @@
@Presubmit
public class TransactionExecutorTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private ClientTransactionHandler mTransactionHandler;
@Mock
@@ -240,29 +248,19 @@
@Test
public void testTransactionResolution() {
- ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
- ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
- transaction.setLifecycleStateRequest(mActivityLifecycleItem);
-
- transaction.preExecute(mTransactionHandler);
- mExecutor.execute(transaction);
-
- InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2,
- mActivityLifecycleItem);
- inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
- inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
- inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
- any());
+ testTransactionResolutionInner();
}
@Test
- public void testExecuteTransactionItems_transactionResolution() {
+ public void testTransactionResolution_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testTransactionResolutionInner();
+ }
+
+ private void testTransactionResolutionInner() {
ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
@@ -286,38 +284,19 @@
@Test
public void testDoNotLaunchDestroyedActivity() {
- final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
- when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
- // Assume launch transaction is still in queue, so there is no client record.
- when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- // An incoming destroy transaction enters binder thread (preExecute).
- final IBinder token = mock(IBinder.class);
- final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
- destroyTransaction.setLifecycleStateRequest(
- DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
- destroyTransaction.preExecute(mTransactionHandler);
- // The activity should be added to to-be-destroyed container.
- assertEquals(1, activitiesToBeDestroyed.size());
-
- // A previous queued launch transaction runs on main thread (execute).
- final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
- final LaunchActivityItem launchItem =
- spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
- launchTransaction.addCallback(launchItem);
- mExecutor.execute(launchTransaction);
-
- // The launch transaction should not be executed because its token is in the
- // to-be-destroyed container.
- verify(launchItem, never()).execute(any(), any());
-
- // After the destroy transaction has been executed, the token should be removed.
- mExecutor.execute(destroyTransaction);
- assertTrue(activitiesToBeDestroyed.isEmpty());
+ testDoNotLaunchDestroyedActivityInner();
}
@Test
- public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+ public void testDoNotLaunchDestroyedActivity_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testDoNotLaunchDestroyedActivityInner();
+ }
+
+ private void testDoNotLaunchDestroyedActivityInner() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
// Assume launch transaction is still in queue, so there is no client record.
@@ -350,26 +329,19 @@
@Test
public void testActivityResultRequiredStateResolution() {
- when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- PostExecItem postExecItem = new PostExecItem(ON_RESUME);
-
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(postExecItem);
-
- // Verify resolution that should get to onPause
- mClientRecord.setState(ON_RESUME);
- mExecutor.executeCallbacks(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
-
- // Verify resolution that should get to onStart
- mClientRecord.setState(ON_STOP);
- mExecutor.executeCallbacks(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ testActivityResultRequiredStateResolutionInner();
}
@Test
- public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+ public void testActivityResultRequiredStateResolution_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityResultRequiredStateResolutionInner();
+ }
+
+ private void testActivityResultRequiredStateResolutionInner() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
@@ -379,12 +351,12 @@
// Verify resolution that should get to onPause
mClientRecord.setState(ON_RESUME);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
// Verify resolution that should get to onStart
mClientRecord.setState(ON_STOP);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
}
@@ -523,18 +495,19 @@
@Test(expected = IllegalArgumentException.class)
public void testActivityItemNullRecordThrowsException() {
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- final IBinder token = mock(IBinder.class);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(activityItem);
- when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- mExecutor.executeCallbacks(transaction);
+ testActivityItemNullRecordThrowsExceptionInner();
}
@Test(expected = IllegalArgumentException.class)
- public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+ public void testActivityItemNullRecordThrowsException_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityItemNullRecordThrowsExceptionInner();
+ }
+
+ private void testActivityItemNullRecordThrowsExceptionInner() {
final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
final IBinder token = mock(IBinder.class);
@@ -542,28 +515,24 @@
transaction.addTransactionItem(activityItem);
when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
}
@Test
public void testActivityItemExecute() {
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- when(activityItem.getActivityToken()).thenReturn(mActivityToken);
- transaction.addCallback(activityItem);
- transaction.setLifecycleStateRequest(mActivityLifecycleItem);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- mExecutor.execute(transaction);
-
- final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem);
- inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
- inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
- any());
+ testActivityItemExecuteInner();
}
@Test
- public void testExecuteTransactionItems_activityItemExecute() {
+ public void testActivityItemExecute_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityItemExecuteInner();
+ }
+
+ private void testActivityItemExecuteInner() {
final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index c30d216..aa80013 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -20,6 +20,9 @@
import static android.app.servertransaction.TestUtils.mergedConfig;
import static android.app.servertransaction.TestUtils.referrerIntentList;
import static android.app.servertransaction.TestUtils.resultInfoList;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertEquals;
@@ -36,11 +39,13 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,6 +65,9 @@
@Presubmit
public class TransactionParcelTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private Parcel mParcel;
private IBinder mActivityToken;
@@ -275,6 +283,8 @@
@Test
public void testClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
@@ -300,14 +310,16 @@
@Test
public void testClientTransactionCallbacksOnly() {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
mActivityToken, config());
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
writeAndPrepareForReading(transaction);
@@ -321,12 +333,14 @@
@Test
public void testClientTransactionLifecycleOnly() {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.setLifecycleStateRequest(lifecycleRequest);
+ transaction.addTransactionItem(lifecycleRequest);
writeAndPrepareForReading(transaction);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
index a0149da..1a04bb8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -37,11 +37,13 @@
import androidx.compose.ui.semantics.Role
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.SettingsDialog
+import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsDialogItem
data class ListPreferenceOption(
val id: Int,
val text: String,
+ val summary: String = String()
)
/**
@@ -129,6 +131,14 @@
) {
RadioButton(selected = selected, onClick = null, enabled = enabled)
Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd))
- SettingsDialogItem(text = option.text, enabled = enabled)
+ Column {
+ SettingsDialogItem(text = option.text, enabled = enabled)
+ if (option.summary != String()) {
+ SettingsBody(
+ body = option.summary,
+ maxLines = 1
+ )
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index e36572f..3216e37 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -24,7 +24,6 @@
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -46,14 +45,14 @@
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.weight(1f)) {
- Preference(remember {
+ Preference(
object : PreferenceModel {
override val title = title
override val summary = summary
override val icon = icon
override val onClick = onClick
}
- })
+ )
}
PreferenceDivider()
widget()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
index 796ac48..417ce6e 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -123,6 +123,26 @@
}
@Test
+ fun click_optionsNotEmptyAndItemHasSummary_itemShowSummary() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options =
+ listOf(ListPreferenceOption(id = 1, text = "A", summary = "A_Summary"))
+ override val selectedId = mutableIntStateOf(1)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+ composeTestRule.onNodeWithText("A_Summary").assertIsDisplayed()
+ }
+
+ @Test
fun select() {
val selectedId = mutableIntStateOf(1)
composeTestRule.setContent {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 249fa7f..e489bc5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1702,7 +1702,8 @@
}
public boolean isPrivateProfile() {
- return UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
+ return android.os.Flags.allowPrivateProfile()
+ && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
}
/**
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 213a66e..1ad7d49 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -22,21 +22,30 @@
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
+import android.os.Flags;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ApplicationsStateTest {
+ private static final int APP_ENTRY_ID = 1;
private ApplicationsState.AppEntry mEntry;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
- mEntry = mock(ApplicationsState.AppEntry.class);
- mEntry.info = mock(ApplicationInfo.class);
+ mEntry = new ApplicationsState.AppEntry(
+ ApplicationProvider.getApplicationContext(),
+ mock(ApplicationInfo.class),
+ APP_ENTRY_ID);
}
@Test
@@ -310,6 +319,8 @@
@Test
public void testPrivateProfileFilterDisplaysCorrectApps() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
mEntry.showInPersonalTab = true;
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
@@ -320,4 +331,14 @@
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isTrue();
}
+
+ @Test
+ public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mEntry.showInPersonalTab = false;
+ mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+ assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index 977dbff..84a59b4 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -117,6 +117,30 @@
}
@AnyThread
+ void onStartInputResult(@NonNull InputBindResult res, int startInputSeq) {
+ if (mIsProxy) {
+ onStartInputResultInternal(res, startInputSeq);
+ } else {
+ mHandler.post(() -> onStartInputResultInternal(res, startInputSeq));
+ }
+ }
+
+ @AnyThread
+ private void onStartInputResultInternal(@NonNull InputBindResult res, int startInputSeq) {
+ try {
+ mTarget.onStartInputResult(res, startInputSeq);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ } finally {
+ // Dispose the channel if the input method is not local to this process
+ // because the remote proxy will get its own copy when unparceled.
+ if (res.channel != null && mIsProxy) {
+ res.channel.dispose();
+ }
+ }
+ }
+
+ @AnyThread
void onBindAccessibilityService(@NonNull InputBindResult res, int id) {
if (mIsProxy) {
onBindAccessibilityServiceInternal(res, id);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index bc169ca..5574d18 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1539,7 +1539,13 @@
@Override
public void onStart() {
mService.publishLocalService();
- publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
+ IInputMethodManager.Stub service;
+ if (Flags.useZeroJankProxy()) {
+ service = new ZeroJankProxy(mService.mHandler::post, mService);
+ } else {
+ service = mService;
+ }
+ publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@@ -2216,6 +2222,14 @@
}
}
+ @Nullable
+ ClientState getClientState(IInputMethodClient client) {
+ synchronized (ImfLock.class) {
+ return mClientController.getClient(client.asBinder());
+ }
+ }
+
+ // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -3741,6 +3755,20 @@
return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS;
}
+ //TODO(b/293640003): merge with startInputOrWindowGainedFocus once Flags.useZeroJankProxy()
+ // is enabled.
+ @Override
+ public void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+ // implemented by ZeroJankProxy
+ }
+
@NonNull
@Override
public InputBindResult startInputOrWindowGainedFocus(
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
new file mode 100644
index 0000000..692fd7dc
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+/*
+ * Copyright (C) 2024 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.server.inputmethod;
+
+import static com.android.server.inputmethod.InputMethodManagerService.TAG;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+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.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.view.IInputMethodManager;
+
+import java.io.FileDescriptor;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+/**
+ * A proxy that processes all {@link IInputMethodManager} calls asynchronously.
+ * @hide
+ */
+public class ZeroJankProxy extends IInputMethodManager.Stub {
+
+ private final IInputMethodManager mInner;
+ private final Executor mExecutor;
+
+ ZeroJankProxy(Executor executor, IInputMethodManager inner) {
+ mInner = inner;
+ mExecutor = executor;
+ }
+
+ private void offload(ThrowingRunnable r) {
+ offloadInner(r);
+ }
+
+ private void offload(Runnable r) {
+ offloadInner(r);
+ }
+
+ private void offloadInner(Runnable r) {
+ boolean useThrowingRunnable = r instanceof ThrowingRunnable;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ final long inner = Binder.clearCallingIdentity();
+ // Restoring calling identity, so we can still do permission checks on caller.
+ Binder.restoreCallingIdentity(identity);
+ try {
+ try {
+ if (useThrowingRunnable) {
+ ((ThrowingRunnable) r).runOrThrow();
+ } else {
+ r.run();
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in async call", e);
+ throw ExceptionUtils.propagate(e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(inner);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId) throws RemoteException {
+ offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
+ }
+
+ @Override
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException {
+ return mInner.getCurrentInputMethodInfoAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList(
+ int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException {
+ return mInner.getInputMethodList(userId, directBootAwareness);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException {
+ return mInner.getEnabledInputMethodList(userId);
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, int userId)
+ throws RemoteException {
+ return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
+ userId);
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException {
+ return mInner.getLastInputMethodSubtype(userId);
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ int lastClickTooType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason)
+ throws RemoteException {
+ offload(() -> mInner.showSoftInput(client, windowToken, statsToken, flags, lastClickTooType,
+ resultReceiver, reason));
+ return true;
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)
+ throws RemoteException {
+ offload(() -> mInner.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason));
+ return true;
+ }
+
+ @Override
+ public void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq)
+ throws RemoteException {
+ offload(() -> {
+ InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
+ windowToken, startInputFlags, softInputMode, windowFlags,
+ editorInfo,
+ inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion,
+ userId, imeDispatcher);
+ sendOnStartInputResult(client, result, startInputSeq);
+ });
+ }
+
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @Override
+ public InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher)
+ throws RemoteException {
+ // Should never be called when flag is enabled i.e. when this proxy is used.
+ return null;
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient client,
+ int auxiliarySubtypeMode)
+ throws RemoteException {
+ offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
+ }
+
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)
+ throws RemoteException {
+ mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public boolean isInputMethodPickerShownForTest() throws RemoteException {
+ super.isInputMethodPickerShownForTest_enforcePermission();
+ return mInner.isInputMethodPickerShownForTest();
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException {
+ return mInner.getCurrentInputMethodSubtype(userId);
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId) throws RemoteException {
+ mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId);
+ }
+
+ @Override
+ public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException {
+ mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client)
+ throws RemoteException {
+ return mInner.getInputMethodWindowVisibleHeight(client);
+ }
+
+ @Override
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible)
+ throws RemoteException {
+ // Already async TODO(b/293640003): ordering issues?
+ mInner.reportPerceptibleAsync(windowToken, perceptible);
+ }
+
+ @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @Override
+ public void removeImeSurface() throws RemoteException {
+ mInner.removeImeSurface();
+ }
+
+ @Override
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException {
+ mInner.removeImeSurfaceFromWindowAsync(windowToken);
+ }
+
+ @Override
+ public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException {
+ mInner.startProtoDump(bytes, i, s);
+ }
+
+ @Override
+ public boolean isImeTraceEnabled() throws RemoteException {
+ return mInner.isImeTraceEnabled();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void startImeTrace() throws RemoteException {
+ mInner.startImeTrace();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void stopImeTrace() throws RemoteException {
+ mInner.stopImeTrace();
+ }
+
+ @Override
+ public void startStylusHandwriting(IInputMethodClient client)
+ throws RemoteException {
+ offload(() -> mInner.startStylusHandwriting(client));
+ }
+
+ @Override
+ public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ offload(() -> mInner.startConnectionlessStylusHandwriting(
+ client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName,
+ callback));
+ }
+
+ @Override
+ public boolean acceptStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ try {
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ return mInner.acceptStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }, this::offload).get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void prepareStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ offload(() -> mInner.prepareStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName));
+ }
+
+ @Override
+ public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless)
+ throws RemoteException {
+ return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless);
+ }
+
+ @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @Override
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client)
+ throws RemoteException {
+ mInner.addVirtualStylusIdForTestSession(client);
+ }
+
+ @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @Override
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout)
+ throws RemoteException {
+ mInner.setStylusWindowIdleTimeoutForTest(client, timeout);
+ }
+
+ @Override
+ public IImeTracker getImeTrackerService() throws RemoteException {
+ return mInner.getImeTrackerService();
+ }
+
+ @BinderThread
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ ((InputMethodManagerService) mInner).onShellCommand(
+ in, out, err, args, callback, resultReceiver);
+ }
+
+ private void sendOnStartInputResult(
+ IInputMethodClient client, InputBindResult res, int startInputSeq) {
+ InputMethodManagerService service = (InputMethodManagerService) mInner;
+ final ClientState cs = service.getClientState(client);
+ if (cs != null && cs.mClient != null) {
+ cs.mClient.onStartInputResult(res, startInputSeq);
+ } else {
+ // client is unbound.
+ Slog.i(TAG, "Client that requested startInputOrWindowGainedFocus is no longer"
+ + " bound. InputBindResult: " + res + " for startInputSeq: " + startInputSeq);
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 5b4fb3e..e48e4e8 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -87,11 +87,7 @@
void scheduleTransactionItemNow(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
- } else {
- clientTransaction.addCallback(transactionItem);
- }
+ clientTransaction.addTransactionItem(transactionItem);
scheduleTransaction(clientTransaction);
}
@@ -115,11 +111,8 @@
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
- } else {
- clientTransaction.addCallback(transactionItem);
- }
+ clientTransaction.addTransactionItem(transactionItem);
+
scheduleTransaction(clientTransaction);
}
}
@@ -160,8 +153,8 @@
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- clientTransaction.addCallback(transactionItem);
- clientTransaction.setLifecycleStateRequest(lifecycleItem);
+ clientTransaction.addTransactionItem(transactionItem);
+ clientTransaction.addTransactionItem(lifecycleItem);
scheduleTransaction(clientTransaction);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 838ce86..10cbc66 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1590,7 +1590,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, activityResultItem);
} else {
- transaction.addCallback(activityResultItem);
+ transaction.addTransactionItem(activityResultItem);
}
}
}
@@ -1602,7 +1602,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, newIntentItem);
} else {
- transaction.addCallback(newIntentItem);
+ transaction.addTransactionItem(newIntentItem);
}
}
@@ -1624,7 +1624,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, resumeActivityItem);
} else {
- transaction.setLifecycleStateRequest(resumeActivityItem);
+ transaction.addTransactionItem(resumeActivityItem);
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
}
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
index 7e97fa3..9b527dc 100644
--- a/tests/BootImageProfileTest/AndroidTest.xml
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for BootImageProfileTest">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
furthermore the changes in /data/local.prop don't actually seem to get picked up.
-->
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 70e4a71..443de8e 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
index 502c1b4..cb69c0e 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
index 591b2fa..1c6d1b3 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index 0137a85..c51da05 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index 37a91e1..ab23401 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ