Merge "Enable forced focus for resumed split screen activities" into tm-qpr-dev am: f1fc4e774a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20620288

Change-Id: I321b9cf1c04d4b291b4da1739376fc791e03e422
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mariia Sandrikova
2022-12-26 22:11:43 +00:00
committed by Automerger Merge Worker
20 changed files with 149 additions and 27 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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<ResumeActivityItem> 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 + "}";
}
}

View File

@@ -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 */,

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -5291,6 +5291,11 @@
TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
<bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
<!-- Whether sending compat fake focus for split screen resumed activities is enabled.
Needed because some game engines wait to get focus before drawing the content of
the app which isn't guaranteed by default in multi-window modes. -->
<bool name="config_isCompatFakeFocusEnabled">false</bool>
<!-- Whether camera compat treatment is enabled for issues caused by orientation mismatch
between camera buffers and an app window. This includes force rotation of fixed
orientation activities connected to the camera in fullscreen and showing a tooltip in

View File

@@ -4484,6 +4484,7 @@
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
<java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
<java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />

View File

@@ -692,7 +692,8 @@ public class ActivityThreadTest {
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
null, 0, new MergedConfiguration(), false /* preserveWindow */);
final ResumeActivityItem resumeStateRequest =
ResumeActivityItem.obtain(true /* isForward */);
ResumeActivityItem.obtain(true /* isForward */,
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
transaction.addCallback(callbackItem);
@@ -703,7 +704,8 @@ public class ActivityThreadTest {
private static ClientTransaction newResumeTransaction(Activity activity) {
final ResumeActivityItem resumeStateRequest =
ResumeActivityItem.obtain(true /* isForward */);
ResumeActivityItem.obtain(true /* isForward */,
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
transaction.setLifecycleStateRequest(resumeStateRequest);

View File

@@ -238,15 +238,15 @@ public class ObjectPoolTests {
@Test
public void testRecycleResumeActivityItem() {
ResumeActivityItem emptyItem = ResumeActivityItem.obtain(false);
ResumeActivityItem item = ResumeActivityItem.obtain(3, true);
ResumeActivityItem emptyItem = ResumeActivityItem.obtain(false, false);
ResumeActivityItem item = ResumeActivityItem.obtain(3, true, false);
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
item.recycle();
assertEquals(item, emptyItem);
ResumeActivityItem item2 = ResumeActivityItem.obtain(2, true);
ResumeActivityItem item2 = ResumeActivityItem.obtain(2, true, false);
assertSame(item, item2);
assertFalse(item2.equals(emptyItem));
}

View File

@@ -250,7 +250,7 @@ public class TransactionParcelTests {
public void testResume() {
// Write to parcel
ResumeActivityItem item = ResumeActivityItem.obtain(27 /* procState */,
true /* isForward */);
true /* isForward */, false /* shouldSendCompatFakeFocus */);
writeAndPrepareForReading(item);
// Read from parcel and assert

View File

@@ -297,6 +297,20 @@ public class ViewRootImplTest {
}
}
@Test
public void whenDispatchFakeFocus_focusDoesNotPersist() throws Exception {
View view = new View(sContext);
attachViewToWindow(view);
view.clearFocus();
assertThat(view.hasWindowFocus()).isFalse();
mViewRootImpl = view.getViewRootImpl();
mViewRootImpl.dispatchCompatFakeFocus();
assertThat(view.hasWindowFocus()).isFalse();
}
/**
* When window doesn't have focus, keys should be dropped.
*/

View File

@@ -294,7 +294,7 @@ public class ActivityThreadClientTest {
private void resumeActivity(ActivityClientRecord r) {
mThread.handleResumeActivity(r, true /* finalStateRequest */,
true /* isForward */, "test");
true /* isForward */, false /* shouldSendCompatFakeFocus */, "test");
}
private void pauseActivity(ActivityClientRecord r) {

View File

@@ -9351,7 +9351,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
preserveWindow);
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(isTransitionForward());
lifecycleItem = ResumeActivityItem.obtain(isTransitionForward(),
shouldSendCompatFakeFocus());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
@@ -10118,6 +10119,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
/**
* Whether we should send fake focus when the activity is resumed. This is done because some
* game engines wait to get focus before drawing the content of the app.
*/
// TODO(b/263593361): Explore enabling compat fake focus for freeform.
// TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
// covered with bubbles.
boolean shouldSendCompatFakeFocus() {
return mWmService.mLetterboxConfiguration.isCompatFakeFocusEnabled() && inMultiWindowMode()
&& !inPinnedWindowingMode() && !inFreeformWindowingMode();
}
static class Builder {
private final ActivityTaskManagerService mAtmService;
private WindowProcessController mCallerApp;

View File

@@ -918,7 +918,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(isTransitionForward);
lifecycleItem = ResumeActivityItem.obtain(isTransitionForward,
r.shouldSendCompatFakeFocus());
} else {
lifecycleItem = PauseActivityItem.obtain();
}

View File

@@ -214,7 +214,8 @@ final class DisplayRotationCompatPolicy {
activity.app.getThread(), activity.token);
transaction.addCallback(
RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(
/* isForward */ false, /* shouldSendCompatFakeFocus */ false));
activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
mHandler.postDelayed(
() -> onActivityRefreshed(activity),

View File

@@ -191,6 +191,11 @@ final class LetterboxConfiguration {
// Allows to enable letterboxing strategy for translucent activities ignoring flags.
private boolean mTranslucentLetterboxingOverrideEnabled;
// Whether sending compat fake focus is enabled for unfocused apps in splitscreen. Some game
// engines wait to get focus before drawing the content of the app so this needs to be used
// otherwise the apps get blacked out when they are resumed and do not have focus yet.
private boolean mIsCompatFakeFocusEnabled;
// Whether camera compatibility treatment is enabled.
// See DisplayRotationCompatPolicy for context.
private final boolean mIsCameraCompatTreatmentEnabled;
@@ -259,6 +264,8 @@ final class LetterboxConfiguration {
R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
mIsCompatFakeFocusEnabled = mContext.getResources()
.getBoolean(R.bool.config_isCompatFakeFocusEnabled);
}
/**
@@ -970,6 +977,13 @@ final class LetterboxConfiguration {
"enable_translucent_activity_letterbox", false);
}
// TODO(b/262866240): Add listener to check for device config property
/** Whether fake sending focus is enabled for unfocused apps in splitscreen */
boolean isCompatFakeFocusEnabled() {
return mIsCompatFakeFocusEnabled && DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, "enable_compat_fake_focus", true);
}
/** Whether camera compatibility treatment is enabled. */
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
return mIsCameraCompatTreatmentEnabled

View File

@@ -1472,7 +1472,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
next.abortAndClearOptionsAnimation();
transaction.setLifecycleStateRequest(
ResumeActivityItem.obtain(next.app.getReportedProcState(),
dc.isNextTransitionForward()));
dc.isNextTransitionForward(), next.shouldSendCompatFakeFocus()));
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);

View File

@@ -398,7 +398,8 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
final ClientTransaction transaction = ClientTransaction.obtain(
mActivity.app.getThread(), mActivity.token);
transaction.addCallback(RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(
/* isForward */ false, /* shouldSendCompatFakeFocus */ false));
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
.scheduleTransaction(eq(transaction));