diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 9385e8760a022..78d09abe4b4fb 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4164,7 +4164,8 @@ public final class ActivityThread extends ClientTransactionHandler private void scheduleResume(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); - transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false)); + transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false, + /* shouldSendCompatFakeFocus */ false)); executeTransaction(transaction); } @@ -4791,7 +4792,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest, - boolean isForward, String reason) { + boolean isForward, boolean shouldSendCompatFakeFocus, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); @@ -4898,6 +4899,16 @@ public final class ActivityThread extends ClientTransactionHandler if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } + + if (shouldSendCompatFakeFocus) { + // Attaching to a window is asynchronous with the activity being resumed, + // so it's possible we will need to send a fake focus event after attaching + if (impl != null) { + impl.dispatchCompatFakeFocus(); + } else { + r.window.getDecorView().fakeFocusAfterAttachingToWindow(); + } + } } r.nextIdle = mNewActivities; diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 2c70c4e901576..d7fa732cfb3d4 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -109,7 +109,8 @@ public abstract class ClientTransactionHandler { * @param reason Reason for performing this operation. */ public abstract void handleResumeActivity(@NonNull ActivityClientRecord r, - boolean finalStateRequest, boolean isForward, String reason); + boolean finalStateRequest, boolean isForward, boolean shouldSendCompatFakeFocus, + String reason); /** * Notify the activity about top resumed state change. diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index e6fdc006615a1..222f8ca61e098 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -39,6 +39,9 @@ public class ResumeActivityItem extends ActivityLifecycleItem { private int mProcState; private boolean mUpdateProcState; private boolean mIsForward; + // Whether we should send compat fake focus when the activity is resumed. This is needed + // because some game engines wait to get focus before drawing the content of the app. + private boolean mShouldSendCompatFakeFocus; @Override public void preExecute(ClientTransactionHandler client, IBinder token) { @@ -52,7 +55,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward, - "RESUME_ACTIVITY"); + mShouldSendCompatFakeFocus, "RESUME_ACTIVITY"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -74,7 +77,8 @@ public class ResumeActivityItem extends ActivityLifecycleItem { private ResumeActivityItem() {} /** Obtain an instance initialized with provided params. */ - public static ResumeActivityItem obtain(int procState, boolean isForward) { + public static ResumeActivityItem obtain(int procState, boolean isForward, + boolean shouldSendCompatFakeFocus) { ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class); if (instance == null) { instance = new ResumeActivityItem(); @@ -82,12 +86,13 @@ public class ResumeActivityItem extends ActivityLifecycleItem { instance.mProcState = procState; instance.mUpdateProcState = true; instance.mIsForward = isForward; + instance.mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus; return instance; } /** Obtain an instance initialized with provided params. */ - public static ResumeActivityItem obtain(boolean isForward) { + public static ResumeActivityItem obtain(boolean isForward, boolean shouldSendCompatFakeFocus) { ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class); if (instance == null) { instance = new ResumeActivityItem(); @@ -95,6 +100,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { instance.mProcState = ActivityManager.PROCESS_STATE_UNKNOWN; instance.mUpdateProcState = false; instance.mIsForward = isForward; + instance.mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus; return instance; } @@ -105,6 +111,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { mProcState = ActivityManager.PROCESS_STATE_UNKNOWN; mUpdateProcState = false; mIsForward = false; + mShouldSendCompatFakeFocus = false; ObjectPool.recycle(this); } @@ -117,6 +124,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { dest.writeInt(mProcState); dest.writeBoolean(mUpdateProcState); dest.writeBoolean(mIsForward); + dest.writeBoolean(mShouldSendCompatFakeFocus); } /** Read from Parcel. */ @@ -124,6 +132,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { mProcState = in.readInt(); mUpdateProcState = in.readBoolean(); mIsForward = in.readBoolean(); + mShouldSendCompatFakeFocus = in.readBoolean(); } public static final @NonNull Creator CREATOR = @@ -147,7 +156,8 @@ public class ResumeActivityItem extends ActivityLifecycleItem { } final ResumeActivityItem other = (ResumeActivityItem) o; return mProcState == other.mProcState && mUpdateProcState == other.mUpdateProcState - && mIsForward == other.mIsForward; + && mIsForward == other.mIsForward + && mShouldSendCompatFakeFocus == other.mShouldSendCompatFakeFocus; } @Override @@ -156,12 +166,14 @@ public class ResumeActivityItem extends ActivityLifecycleItem { result = 31 * result + mProcState; result = 31 * result + (mUpdateProcState ? 1 : 0); result = 31 * result + (mIsForward ? 1 : 0); + result = 31 * result + (mShouldSendCompatFakeFocus ? 1 : 0); return result; } @Override public String toString() { return "ResumeActivityItem{procState=" + mProcState - + ",updateProcState=" + mUpdateProcState + ",isForward=" + mIsForward + "}"; + + ",updateProcState=" + mUpdateProcState + ",isForward=" + mIsForward + + ",shouldSendCompatFakeFocus=" + mShouldSendCompatFakeFocus + "}"; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 1ff0b796fb1ec..c8f7d100a3985 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -226,7 +226,8 @@ public class TransactionExecutor { break; case ON_RESUME: mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */, - r.isForward, "LIFECYCLER_RESUME_ACTIVITY"); + r.isForward, false /* shouldSendCompatFakeFocus */, + "LIFECYCLER_RESUME_ACTIVITY"); break; case ON_PAUSE: mTransactionHandler.handlePauseActivity(r, false /* finished */, diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index cb6aa09cc6dbc..5311b09e609db 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -202,7 +202,8 @@ public class TransactionExecutorHelper { lifecycleItem = StopActivityItem.obtain(0 /* configChanges */); break; default: - lifecycleItem = ResumeActivityItem.obtain(false /* isForward */); + lifecycleItem = ResumeActivityItem.obtain(false /* isForward */, + false /* shouldSendCompatFakeFocus */); break; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index be143f4d5f0a0..b1008919a1253 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -21268,6 +21268,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return Math.max(vis1, vis2); } + private boolean mShouldFakeFocus = false; + + /** + * Fake send a focus event after attaching to window. + * See {@link android.view.ViewRootImpl#dispatchCompatFakeFocus()} for details. + * @hide + */ + public void fakeFocusAfterAttachingToWindow() { + mShouldFakeFocus = true; + } + /** * @param info the {@link android.view.View.AttachInfo} to associated with * this view @@ -21336,6 +21347,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyEnterOrExitForAutoFillIfNeeded(true); notifyAppearedOrDisappearedForContentCaptureIfNeeded(true); + + if (mShouldFakeFocus) { + getViewRootImpl().dispatchCompatFakeFocus(); + mShouldFakeFocus = false; + } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4b9fd64996598..5f04d58469771 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -258,6 +258,7 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_CONTENT_CAPTURE = false || LOCAL_LOGV; private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV; private static final boolean DEBUG_BLAST = false || LOCAL_LOGV; + private static final int LOGTAG_INPUT_FOCUS = 62001; /** * Set to false if we do not want to use the multi threaded renderer even though @@ -3810,8 +3811,7 @@ public final class ViewRootImpl implements ViewParent, } if (mAdded) { - dispatchFocusEvent(hasWindowFocus); - + dispatchFocusEvent(hasWindowFocus, false /* fakeFocus */); // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. mImeFocusController.onPostWindowFocus( @@ -3846,7 +3846,33 @@ public final class ViewRootImpl implements ViewParent, } } - private void dispatchFocusEvent(boolean hasWindowFocus) { + /** + * Send a fake focus event for unfocused apps in split screen as some game engines wait to + * get focus before drawing the content of the app. This will be used so that apps do not get + * blacked out when they are resumed and do not have focus yet. + * + * {@hide} + */ + // TODO(b/263094829): Investigate dispatching this for onPause as well + public void dispatchCompatFakeFocus() { + boolean aboutToHaveFocus = false; + synchronized (this) { + aboutToHaveFocus = mWindowFocusChanged && mUpcomingWindowFocus; + } + final boolean alreadyHaveFocus = mAttachInfo.mHasWindowFocus; + if (aboutToHaveFocus || alreadyHaveFocus) { + // Do not need to toggle focus if app doesn't need it, or has focus. + return; + } + EventLog.writeEvent(LOGTAG_INPUT_FOCUS, + "Giving fake focus to " + mBasePackageName, "reason=unity bug workaround"); + dispatchFocusEvent(true /* hasWindowFocus */, true /* fakeFocus */); + EventLog.writeEvent(LOGTAG_INPUT_FOCUS, + "Removing fake focus from " + mBasePackageName, "reason=timeout callback"); + dispatchFocusEvent(false /* hasWindowFocus */, true /* fakeFocus */); + } + + private void dispatchFocusEvent(boolean hasWindowFocus, boolean fakeFocus) { profileRendering(hasWindowFocus); if (hasWindowFocus && mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { mFullRedrawNeeded = true; @@ -3872,8 +3898,10 @@ public final class ViewRootImpl implements ViewParent, } mAttachInfo.mHasWindowFocus = hasWindowFocus; - mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */); - mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes); + if (!fakeFocus) { + mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */); + mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes); + } if (mView != null) { mAttachInfo.mKeyDispatchState.reset(); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 36e39988ccab1..4215774609ca6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5291,6 +5291,11 @@ TODO(b/255532890) Enable when ignoreOrientationRequest is set --> false + + false +