0

Reland "[clank] Set web contents visibility based on occlusion"

This is a reland of commit d9120f8afd

Original change's description:
> [clank] Set web contents visibility based on occlusion
>
> This CL uses the trusted presentation API to determine occlusion for
> clank windows and plumbs it through to web contents visibility.
>
>   from Android system server
>
> Low-Coverage-Reason: HARD_TO_TEST This code interfaces with messages
> Bug: 349735915
> Test: CQ
> Change-Id: I4b6d14cd36bea6e1b56aca8714665281d0cbc76b
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5754164
> Commit-Queue: Eliot Courtney <edcourtney@chromium.org>
> Reviewed-by: Ted Choc <tedchoc@chromium.org>
> Reviewed-by: Richard (Torne) Coles <torne@chromium.org>
> Reviewed-by: David Trainor <dtrainor@chromium.org>
> Reviewed-by: Simeon Anfinrud <sanfin@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1380048}

Low-Coverage-Reason: HARD_TO_TEST Interfaces with Android system server
Bug: 349735915
Change-Id: Ib8e35e9ab127cec262bda2f1da02b57a505668c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6013410
Commit-Queue: Eliot Courtney <edcourtney@chromium.org>
Reviewed-by: Richard (Torne) Coles <torne@chromium.org>
Reviewed-by: Ted Choc <tedchoc@chromium.org>
Reviewed-by: Simeon Anfinrud <sanfin@chromium.org>
Reviewed-by: David Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1383440}
This commit is contained in:
Eliot Courtney
2024-11-15 06:33:40 +00:00
committed by Chromium LUCI CQ
parent 575e2da0f0
commit 6b9b09c6e2
46 changed files with 258 additions and 74 deletions
android_webview/java/src/org/chromium/android_webview
chrome
android
browser
commerce
merchant_viewer
android
javatests
src
org
chromium
facilitated_payments
ui
android
internal
java
src
org
chromium
password_manager
android
grouped_affiliations
java
src
org
chromium
chrome
share
android
javatests
src
org
chromium
ui
android
default_browser_promo
java
src
org
chromium
chrome
browser
ui
hats
internal
java
src
org
chromium
omnibox
java
src
org
chromecast/browser/android/apk/src/org/chromium/chromecast/shell
components
browser_ui
contacts_picker
android
java
src
org
chromium
components
browser_ui
photo_picker
android
java
src
org
chromium
components
browser_ui
thin_webview
internal
java
src
org
chromium
components
thinwebview
content/shell/android
browsertests
src
org
chromium
content_shell
shell_apk
src
org
chromium
content_shell_apk
ui/android/java/src/org/chromium/ui/base

@ -1858,11 +1858,14 @@ public class AwContents implements SmartClipProvider {
context,
listenToActivityState,
IntentRequestTracker.createFromActivity(activity),
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
}
wrapper = new WindowAndroidWrapper(activityWindow);
} else {
wrapper = new WindowAndroidWrapper(new WindowAndroid(context));
wrapper =
new WindowAndroidWrapper(
new WindowAndroid(context, /* trackOcclusion= */ false));
}
sContextWindowMap.put(context, wrapper);
}

@ -87,7 +87,8 @@ public class ChromeWindow extends ActivityWindowAndroid {
/* listenToActivityState= */ true,
activityKeyboardVisibilityDelegate,
intentRequestTracker,
insetObserver);
insetObserver,
/* trackOcclusion= */ false);
assert insetObserver != null;
mCompositorViewHolderSupplier = compositorViewHolderSupplier;
mModalDialogManagerSupplier = modalDialogManagerSupplier;

@ -289,7 +289,7 @@ public class WarmupManager {
// These are effectively unused as they will be set when finishing reparenting.
TabDelegateFactory delegateFactory = CustomTabDelegateFactory.createEmpty();
WindowAndroid window = new WindowAndroid(context);
WindowAndroid window = new WindowAndroid(context, /* trackOcclusion= */ false);
// TODO(crbug.com/40174356): Set isIncognito flag here if spare tabs are allowed for
// incognito mode.

@ -100,7 +100,12 @@ public class CreatorActivity extends SnackbarActivity {
IntentRequestTracker intentRequestTracker = IntentRequestTracker.createFromActivity(this);
mWindowAndroid =
new ActivityWindowAndroid(this, false, intentRequestTracker, getInsetObserver());
new ActivityWindowAndroid(
this,
false,
intentRequestTracker,
getInsetObserver(),
/* trackOcclusion= */ false);
TabShareDelegateImpl tabshareDelegate =
new TabShareDelegateImpl(

@ -285,7 +285,7 @@ public class ArchivedTabModelOrchestrator extends TabModelOrchestrator implement
Context context = ContextUtils.getApplicationContext();
// TODO(crbug.com/331841977): Investigate removing the WindowAndroid requirement when
// creating tabs.
mWindow = new WindowAndroid(context);
mWindow = new WindowAndroid(context, /* trackOcclusion= */ false);
mArchivedTabCreator = new ArchivedTabCreator(mWindow);
mRegularTabCreator = regularTabCreator;

@ -65,7 +65,8 @@ public class DeviceLockActivity extends SynchronousInitializationActivity
this,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(this),
getInsetObserver());
getInsetObserver(),
/* trackOcclusion= */ false);
mIntentRequestTracker = mWindowAndroid.getIntentRequestTracker();
Bundle fragmentArgs = getIntent().getBundleExtra(ARGUMENT_FRAGMENT_ARGS);

@ -845,6 +845,7 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa
this,
/* listenToActivityState= */ true,
getIntentRequestTracker(),
getInsetObserver());
getInsetObserver(),
/* trackOcclusion= */ false);
}
}

@ -676,7 +676,8 @@ public class PictureInPictureActivity extends AsyncInitializationActivity {
this,
/* listenToActivityState= */ true,
getIntentRequestTracker(),
getInsetObserver());
getInsetObserver(),
/* trackOcclusion= */ false);
}
@CalledByNative

@ -269,7 +269,8 @@ public class SearchActivity extends AsyncInitializationActivity
/* listenToActivityState= */ true,
new ActivityKeyboardVisibilityDelegate(new WeakReference(this)),
getIntentRequestTracker(),
getInsetObserver()) {
getInsetObserver(),
/* trackOcclusion= */ false) {
@Override
public ModalDialogManager getModalDialogManager() {
return SearchActivity.this.getModalDialogManager();

@ -271,7 +271,8 @@ public class SigninAndHistorySyncActivity extends FirstRunActivityBase
this,
/* listenToActivityState= */ true,
getIntentRequestTracker(),
getInsetObserver());
getInsetObserver(),
/* trackOcclusion= */ false);
}
@Override

@ -98,7 +98,8 @@ public class SyncConsentActivity extends SynchronousInitializationActivity
this,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(this),
getInsetObserver());
getInsetObserver(),
/* trackOcclusion= */ false);
}
return mWindowAndroid;
}

@ -28,6 +28,7 @@ import org.jni_zero.CalledByNative;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;
import org.chromium.base.Callback;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
@ -199,6 +200,9 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
/** Whether or not the Tab is currently visible to the user. */
private boolean mIsHidden = true;
/** Called when the current window's occlusion changes. */
private final Callback<Boolean> mOcclusionCallback = (v) -> updateWebContentsVisibility();
/**
* Importance of the WebContents currently attached to this tab. Note the key difference from
* |mIsHidden| is that a tab is hidden when the application is hidden, but the importance is not
@ -618,6 +622,7 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
// We must check this as an additional condition to detachment for this case to continue
// to work. See https://crbug.com/1501849.
mIsDetached = window == null || !windowHasActivity(window);
updateWebContentsVisibility();
}
private boolean checkAttached() {
@ -922,6 +927,19 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
return mIsDestroyed;
}
private void updateWebContentsVisibility() {
var webContents = getWebContents();
if (webContents == null) return;
if (mIsHidden) {
webContents.updateWebContentsVisibility(Visibility.HIDDEN);
} else if (!mIsDetached && mWindowAndroid.getOcclusionSupplier().get()) {
// If we are not attached to a window, occlusion does not make sense.
webContents.updateWebContentsVisibility(Visibility.OCCLUDED);
} else {
webContents.updateWebContentsVisibility(Visibility.VISIBLE);
}
}
@Override
public final void show(@TabSelectionType int type, @TabLoadIfNeededCaller int caller) {
try {
@ -947,9 +965,7 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
// call.
TabImplJni.get().onShow(mNativeTabAndroid);
if (getWebContents() != null) {
getWebContents().updateWebContentsVisibility(Visibility.VISIBLE);
}
updateWebContentsVisibility();
// If the NativePage was frozen while in the background (see NativePageAssassin),
// recreate the NativePage now.
@ -984,10 +1000,7 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
if (isHidden()) return;
mIsHidden = true;
updateInteractableState();
if (getWebContents() != null) {
getWebContents().updateWebContentsVisibility(Visibility.HIDDEN);
}
updateWebContentsVisibility();
// Allow this tab's NativePage to be frozen if it stays hidden for a while.
NativePageAssassin.getInstance().tabHidden(this);
@ -1257,10 +1270,19 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
void updateWindowAndroid(WindowAndroid windowAndroid) {
// TODO(yusufo): mWindowAndroid can never be null until crbug.com/657007 is fixed.
assert windowAndroid != null;
if (mWindowAndroid != null) {
mWindowAndroid.getOcclusionSupplier().removeObserver(mOcclusionCallback);
}
mWindowAndroid = windowAndroid;
WebContents webContents = getWebContents();
if (webContents != null) webContents.setTopLevelNativeWindow(mWindowAndroid);
windowAndroid.getOcclusionSupplier().addObserver(mOcclusionCallback);
// updateIsDetached will also update the web contents visibility if the
// occlusion has changed.
updateIsDetached(windowAndroid);
}
@ -1714,7 +1736,7 @@ class TabImpl implements Tab, SensitiveContentClient.Observer {
bounds.bottom);
}
initWebContents(webContents);
webContents.updateWebContentsVisibility(Visibility.VISIBLE);
updateWebContentsVisibility();
});
if (didStartLoad) {

@ -78,7 +78,8 @@ public class SelectFileDialogTest {
activity,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(activity),
insetObserver);
insetObserver,
/* trackOcclusion= */ false);
}
@Override

@ -116,7 +116,7 @@ public class BookmarkToolbarTest extends BlankUiTestActivityTestCase {
mActivity = getActivity();
ThreadUtils.runOnUiThreadBlocking(
() -> {
mWindowAndroid = new WindowAndroid(mActivity);
mWindowAndroid = new WindowAndroid(mActivity, /* trackOcclusion= */ false);
mContentView = new LinearLayout(mActivity);
mContentView.setBackgroundColor(Color.WHITE);
FrameLayout.LayoutParams params =

@ -172,7 +172,8 @@ public class OverlayPanelBaseTest {
mActivity,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
OverlayPanelManager panelManager = new OverlayPanelManager();
mExpandPanel =
new MockOverlayPanel(

@ -277,7 +277,8 @@ public class OverlayPanelEventFilterTest {
mActivity,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
mPanel =
new MockOverlayPanel(

@ -176,7 +176,8 @@ public class OverlayPanelManagerTest {
mActivity,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
});
}

@ -99,7 +99,8 @@ public class AndroidPaymentAppFinderUnitTest extends BlankUiTestActivityTestCase
getActivity(),
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(getActivity()),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
});
NativeLibraryTestUtils.loadNativeLibraryAndInitBrowserProcess();

@ -74,7 +74,7 @@ public final class AutofillSaveIbanBottomSheetBridgeTest {
Activity activity = Robolectric.buildActivity(Activity.class).create().get();
// set a MaterialComponents theme which is required for the `OutlinedBox` text field.
activity.setTheme(R.style.Theme_BrowserUI_DayNight);
mWindow = new WindowAndroid(activity);
mWindow = new WindowAndroid(activity, /* trackOcclusion= */ false);
BottomSheetControllerFactory.attach(mWindow, mBottomSheetController);
LayoutManagerAppUtils.attach(mWindow, mLayoutManager);
MockTabModel tabModel = new MockTabModel(mProfile, /* delegate= */ null);

@ -57,7 +57,7 @@ public final class AutofillSaveCardBottomSheetBridgeTest {
public void setUp() {
mJniMocker.mock(AutofillSaveCardBottomSheetBridgeJni.TEST_HOOKS, mBridgeNatives);
Activity activity = Robolectric.buildActivity(Activity.class).create().get();
mWindow = new WindowAndroid(activity);
mWindow = new WindowAndroid(activity, /* trackOcclusion= */ false);
BottomSheetControllerFactory.attach(mWindow, mBottomSheetController);
LayoutManagerAppUtils.attach(mWindow, mLayoutManager);
MockTabModel tabModel = new MockTabModel(mProfile, /* delegate= */ null);

@ -104,7 +104,7 @@ public final class AutofillVcnEnrollBottomSheetBridgeTest {
mJniMocker.mock(AutofillVcnEnrollBottomSheetBridgeJni.TEST_HOOKS, mBridgeNatives);
Activity activity = Robolectric.buildActivity(Activity.class).create().get();
mShadowActivity = shadowOf(activity);
mWindow = new WindowAndroid(activity);
mWindow = new WindowAndroid(activity, /* trackOcclusion= */ false);
when(mPersonalDataManager.getCustomImageForAutofillSuggestionIfAvailable(
ISSUER_ICON_URL,
CardIconSpecs.create(mWindow.getContext().get(), ImageSize.SMALL)))

@ -81,7 +81,7 @@ public final class AutofillVcnEnrollBottomSheetCoordinatorTest {
PersonalDataManagerFactory.setInstanceForTesting(mPersonalDataManager);
Activity activity = buildActivity(Activity.class).create().get();
mWindow = new WindowAndroid(activity);
mWindow = new WindowAndroid(activity, /* trackOcclusion= */ false);
BottomSheetControllerFactory.attach(mWindow, mBottomSheetController);
setUpCreditCardWithCardArtUrl();
mCoordinator =

@ -58,7 +58,7 @@ public final class AutofillVcnEnrollBottomSheetMediatorTest {
public void setUp() {
Activity activity = Robolectric.buildActivity(Activity.class).create().get();
mModel = new PropertyModel.Builder(AutofillVcnEnrollBottomSheetProperties.ALL_KEYS).build();
mWindow = new WindowAndroid(activity);
mWindow = new WindowAndroid(activity, /* trackOcclusion= */ false);
BottomSheetControllerFactory.attach(mWindow, mBottomSheetController);
when(mLifecycle.canBegin()).thenReturn(true);
mMediator = new AutofillVcnEnrollBottomSheetMediator(mContent, mLifecycle, mModel);

@ -203,7 +203,8 @@ public class ShareHelperMultiInstanceUnitTest {
mActivity,
/* listenToActivityState= */ false,
mIntentRequestTracker,
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
}
public SingleWindowTestInstance startShare() {

@ -78,7 +78,8 @@ public class ShareHelperUnitTest {
mActivity,
/* listenToActivityState= */ false,
IntentRequestTracker.createFromActivity(mActivity),
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
mImageUri = Uri.parse(IMAGE_URI);
}

@ -40,6 +40,7 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.Token;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
@ -94,6 +95,7 @@ public class TabUnitTest {
doReturn(mWeakReferenceActivity).when(mWindowAndroid).getActivity();
doReturn(mWeakReferenceContext).when(mWindowAndroid).getContext();
doReturn(new ObservableSupplierImpl<>(false)).when(mWindowAndroid).getOcclusionSupplier();
doReturn(mActivity).when(mWeakReferenceActivity).get();
doReturn(mContext).when(mWeakReferenceContext).get();
doReturn(mContext).when(mContext).getApplicationContext();

@ -229,6 +229,7 @@ public class ArchivedTabModelSelectorImplTest {
WindowAndroid window = mock(WindowAndroid.class);
WeakReference<Context> weakContext = new WeakReference<>(mContext);
when(window.getContext()).thenReturn(weakContext);
doReturn(new ObservableSupplierImpl<>(false)).when(window).getOcclusionSupplier();
tab.updateAttachment(window, mTabDelegateFactory);
Assert.assertEquals(

@ -10,6 +10,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ -31,6 +32,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.chromium.base.Callback;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ActivityType;
@ -112,6 +114,7 @@ public class TabModelImplUnitTest {
when(mWindowAndroid.getActivity()).thenReturn(mWeakReferenceActivity);
when(mWindowAndroid.getContext()).thenReturn(mWeakReferenceContext);
doReturn(new ObservableSupplierImpl<>(false)).when(mWindowAndroid).getOcclusionSupplier();
when(mTabGroupModelFilter.getValidPosition(any(), anyInt()))
.thenAnswer(i -> i.getArguments()[1]);

@ -320,6 +320,7 @@ public class TabModelSelectorImplTest {
WindowAndroid window = mock(WindowAndroid.class);
WeakReference<Context> weakContext = new WeakReference<>(mContext);
when(window.getContext()).thenReturn(weakContext);
doReturn(new ObservableSupplierImpl<>(false)).when(window).getOcclusionSupplier();
tab.updateAttachment(window, mTabDelegateFactory);
Assert.assertEquals(

@ -86,7 +86,7 @@ public class MerchantTrustBottomSheetCoordinatorTest extends BlankUiTestActivity
mActivity = getActivity();
ThreadUtils.runOnUiThreadBlocking(
() -> {
mWindowAndroid = new WindowAndroid(mActivity);
mWindowAndroid = new WindowAndroid(mActivity, /* trackOcclusion= */ false);
mDetailsTabCoordinator =
new MerchantTrustBottomSheetCoordinator(
mActivity,

@ -111,7 +111,7 @@ public class FacilitatedPaymentsPaymentMethodsViewBridgeTest {
@Before
public void setUp() {
mApplicationContext = ApplicationProvider.getApplicationContext();
mWindow = new WindowAndroid(mApplicationContext);
mWindow = new WindowAndroid(mApplicationContext, /* trackOcclusion= */ false);
BottomSheetControllerFactory.attach(mWindow, mBottomSheetController);
mViewBridge =
FacilitatedPaymentsPaymentMethodsViewBridge.create(

@ -58,7 +58,9 @@ public class AcknowledgeGroupedCredentialSheetModuleTest {
public void setUp() {
MockitoAnnotations.openMocks(this);
mJniMocker.mock(AcknowledgeGroupedCredentialSheetBridgeJni.TEST_HOOKS, mBridgeJniMock);
mWindowAndroid = new WindowAndroid(ContextUtils.getApplicationContext());
mWindowAndroid =
new WindowAndroid(
ContextUtils.getApplicationContext(), /* trackOcclusion= */ false);
setUpBottomSheetController();
mBridge = new AcknowledgeGroupedCredentialSheetBridge(TEST_NATIVE_POINTER, mWindowAndroid);
}

@ -116,7 +116,7 @@ public class SaveBitmapDelegateTest {
private int mPermissionResult = PackageManager.PERMISSION_GRANTED;
public TestWindowAndroid(Context context) {
super(context);
super(context, /* trackOcclusion= */ false);
}
public void setPermissionResults(int result) {

@ -189,7 +189,8 @@ public class AndroidShareSheetControllerUnitTest {
mActivity,
false,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
mPrintCallback = new PayloadCallbackHelper<>();
// Set up mock tab
doReturn(mWindow).when(mTab).getWindowAndroid();

@ -77,7 +77,8 @@ public class DefaultBrowserPromoUtilsTest {
mActivity,
false,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
TrackerFactory.setTrackerForTests(mMockTracker);
MessagesFactory.attachMessageDispatcher(mWindowAndroid, mMockMessageDispatcher);
SearchEngineChoiceService.setInstanceForTests(mMockSearchEngineChoiceService);

@ -72,7 +72,8 @@ public class SurveyUiDelegateBridgeUnitTest {
mActivity,
false,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
MessagesFactory.attachMessageDispatcher(mWindow, mMockMessageDispatcher);
TabModelSelectorSupplier.setInstanceForTesting(mTabModelSelector);
}

@ -115,7 +115,7 @@ public final class StatusMediatorUnitTest {
mContext =
new ContextThemeWrapper(
ContextUtils.getApplicationContext(), R.style.Theme_BrowserUI_DayNight);
mWindowAndroid = new WindowAndroid(mContext);
mWindowAndroid = new WindowAndroid(mContext, /* trackOcclusion= */ false);
SearchEngineUtils.setInstanceForTesting(mSearchEngineUtils);

@ -124,7 +124,7 @@ public class VoiceRecognitionHandlerUnitTest {
var activity = Robolectric.buildActivity(Activity.class).setup().get();
mProfileSupplier = new ObservableSupplierImpl<>();
mWindowAndroid = spy(new WindowAndroid(activity));
mWindowAndroid = spy(new WindowAndroid(activity, /* trackOcclusion= */ false));
mHandler = spy(new VoiceRecognitionHandler(mDelegate, mProfileSupplier));
mHandler.addObserver(mObserver);

@ -42,7 +42,8 @@ class CastWebContentsScopes {
activity,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(activity),
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
},
backgroundColor);
}
@ -51,24 +52,38 @@ class CastWebContentsScopes {
Activity activity, FrameLayout layout, @ColorInt int backgroundColor) {
layout.setBackgroundColor(backgroundColor);
return onLayoutInternal(
activity, layout, () -> new WindowAndroid(activity), backgroundColor);
activity,
layout,
() -> new WindowAndroid(activity, /* trackOcclusion= */ false),
backgroundColor);
}
static Observer<WebContents> onLayoutView(Context context, FrameLayout layout,
@ColorInt int backgroundColor, WindowTokenProvider windowTokenProvider) {
static Observer<WebContents> onLayoutView(
Context context,
FrameLayout layout,
@ColorInt int backgroundColor,
WindowTokenProvider windowTokenProvider) {
layout.setBackgroundColor(backgroundColor);
return onLayoutInternal(context, layout, () -> new WindowAndroid(context) {
@Override
public IBinder getWindowToken() {
return windowTokenProvider.provideWindowToken();
}
}, backgroundColor);
return onLayoutInternal(
context,
layout,
() ->
new WindowAndroid(context, /* trackOcclusion= */ false) {
@Override
public IBinder getWindowToken() {
return windowTokenProvider.provideWindowToken();
}
},
backgroundColor);
}
// Note: the |windowFactory| should create a new instance of a WindowAndroid each time it is
// invoked.
private static Observer<WebContents> onLayoutInternal(Context context, FrameLayout layout,
Supplier<WindowAndroid> windowFactory, @ColorInt int backgroundColor) {
private static Observer<WebContents> onLayoutInternal(
Context context,
FrameLayout layout,
Supplier<WindowAndroid> windowFactory,
@ColorInt int backgroundColor) {
return (WebContents webContents) -> {
WindowAndroid window = windowFactory.get();
ContentViewRenderView contentViewRenderView =
@ -116,7 +131,7 @@ class CastWebContentsScopes {
public static Observer<WebContents> withoutLayout(Context context) {
return (WebContents webContents) -> {
WindowAndroid window = new WindowAndroid(context);
WindowAndroid window = new WindowAndroid(context, /* trackOcclusion= */ false);
ContentView contentView = ContentView.createContentView(context, webContents);
WebContentsRegistry.initializeWebContents(webContents, contentView, window);
// Enable display of current webContents.

@ -145,7 +145,8 @@ public class ContactsPickerDialogTest
mActivity,
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(mActivity),
mInsetObserver);
mInsetObserver,
/* trackOcclusion= */ false);
});
mWebContents = Mockito.mock(WebContents.class);
when(mWebContents.getTopLevelNativeWindow()).thenReturn(mWindowAndroid);

@ -139,7 +139,8 @@ public class PhotoPickerDialogTest extends BlankUiTestActivityTestCase
getActivity(),
/* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(getActivity()),
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
});
ThreadUtils.runOnUiThreadBlocking(
() -> {

@ -54,9 +54,10 @@ public class ThinWebViewImpl extends FrameLayout implements ThinWebView {
context,
/* listenToActivityState= */ true,
intentRequestTracker,
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
} else {
mWindowAndroid = new WindowAndroid(context);
mWindowAndroid = new WindowAndroid(context, /* trackOcclusion= */ false);
}
mCompositorView = new CompositorViewImpl(context, mWindowAndroid, constraints);

@ -67,7 +67,8 @@ public abstract class ContentShellBrowserTestActivity extends NativeBrowserTestA
this,
/* listenToActivityState= */ true,
intentRequestTracker,
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
mShellManager.setWindow(mWindowAndroid);
Window wind = this.getWindow();

@ -68,7 +68,8 @@ public class ContentShellActivity extends Activity {
this,
listenToActivityState,
mIntentRequestTracker,
/* insetObserver= */ null);
/* insetObserver= */ null,
/* trackOcclusion= */ false);
mIntentRequestTracker.restoreInstanceState(savedInstanceState);
mShellManager.setWindow(mWindowAndroid);
// Set up the animation placeholder to be the SurfaceView. This disables the

@ -38,12 +38,14 @@ public class ActivityWindowAndroid extends WindowAndroid
* @param context Context wrapping an activity associated with the WindowAndroid.
* @param listenToActivityState Whether to listen to activity state changes.
* @param intentRequestTracker The {@link IntentRequestTracker} of the current activity.
* @param trackOcclusion Whether to track occlusion of the window.
*/
public ActivityWindowAndroid(
Context context,
boolean listenToActivityState,
IntentRequestTracker intentRequestTracker,
@Nullable InsetObserver insetObserver) {
@Nullable InsetObserver insetObserver,
boolean trackOcclusion) {
this(
context,
listenToActivityState,
@ -52,7 +54,8 @@ public class ActivityWindowAndroid extends WindowAndroid
new ActivityKeyboardVisibilityDelegate(
new WeakReference<Activity>(ContextUtils.activityFromContext(context))),
intentRequestTracker,
insetObserver);
insetObserver,
trackOcclusion);
}
/**
@ -62,13 +65,15 @@ public class ActivityWindowAndroid extends WindowAndroid
* @param listenToActivityState Whether to listen to activity state changes.
* @param keyboardVisibilityDelegate Delegate which handles keyboard visibility.
* @param intentRequestTracker The {@link IntentRequestTracker} of the current activity.
* @param trackOcclusion Whether to track occlusion of the window.
*/
public ActivityWindowAndroid(
Context context,
boolean listenToActivityState,
@NonNull ActivityKeyboardVisibilityDelegate keyboardVisibilityDelegate,
IntentRequestTracker intentRequestTracker,
InsetObserver insetObserver) {
InsetObserver insetObserver,
boolean trackOcclusion) {
this(
context,
listenToActivityState,
@ -76,7 +81,8 @@ public class ActivityWindowAndroid extends WindowAndroid
new WeakReference<Activity>(ContextUtils.activityFromContext(context))),
keyboardVisibilityDelegate,
intentRequestTracker,
insetObserver);
insetObserver,
trackOcclusion);
}
/**
@ -86,6 +92,7 @@ public class ActivityWindowAndroid extends WindowAndroid
* @param listenToActivityState Whether to listen to activity state changes.
* @param activityAndroidPermissionDelegate Delegates which handles android permissions.
* @param intentRequestTracker The {@link IntentRequestTracker} of the current activity.
* @param trackOcclusion Whether to track occlusion of the window.
*/
private ActivityWindowAndroid(
Context context,
@ -93,8 +100,9 @@ public class ActivityWindowAndroid extends WindowAndroid
ActivityAndroidPermissionDelegate activityAndroidPermissionDelegate,
ActivityKeyboardVisibilityDelegate activityKeyboardVisibilityDelegate,
IntentRequestTracker intentRequestTracker,
InsetObserver insetObserver) {
super(context, intentRequestTracker, insetObserver);
InsetObserver insetObserver,
boolean trackOcclusion) {
super(context, intentRequestTracker, insetObserver, trackOcclusion);
Activity activity = ContextUtils.activityFromContext(context);
if (activity == null) {
throw new IllegalArgumentException("Context is not and does not wrap an Activity");

@ -24,6 +24,7 @@ import android.view.Surface;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.window.TrustedPresentationThresholds;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@ -43,6 +44,10 @@ import org.chromium.base.ObserverList;
import org.chromium.base.PackageManagerUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.UnownedUserDataHost;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.ui.InsetObserver;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.display.DisplayAndroid;
@ -58,10 +63,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;
/** The window base class that has the minimum functionality. */
@JNINamespace("ui")
public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidObserver {
public class WindowAndroid
implements AndroidPermissionDelegate,
DisplayAndroidObserver,
View.OnAttachStateChangeListener {
private static final String TAG = "WindowAndroid";
private static final ImmutableWeakReference<Activity> NULL_ACTIVITY_WEAK_REF =
new ImmutableWeakReference<>(null);
@ -184,16 +193,28 @@ public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidO
private ModalDialogManager mModalDialogManagerForTesting;
private Consumer<Boolean> mOcclusionObserver;
private final boolean mTrackOcclusion;
/** True when this window is occluded. */
private final ObservableSupplierImpl<Boolean> mOcclusionSupplier =
new ObservableSupplierImpl<>(false);
/**
* @param context The application {@link Context}.
* @param trackOcclusion Whether to track occlusion of the window.
*/
public WindowAndroid(Context context) {
this(context, DisplayAndroid.getNonMultiDisplay(context));
public WindowAndroid(Context context, boolean trackOcclusion) {
this(context, DisplayAndroid.getNonMultiDisplay(context), trackOcclusion);
}
protected WindowAndroid(
Context context, IntentRequestTracker tracker, InsetObserver insetObserver) {
this(context, DisplayAndroid.getNonMultiDisplay(context));
Context context,
IntentRequestTracker tracker,
InsetObserver insetObserver,
boolean trackOcclusion) {
this(context, DisplayAndroid.getNonMultiDisplay(context), trackOcclusion);
mIntentRequestTracker = (IntentRequestTrackerImpl) tracker;
mInsetObserver = insetObserver;
}
@ -201,9 +222,10 @@ public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidO
/**
* @param context The application {@link Context}.
* @param display The application {@link DisplayAndroid}.
* @param trackOcclusion Whether to track occlusion of the window.
*/
@SuppressLint("UseSparseArrays")
protected WindowAndroid(Context context, DisplayAndroid display) {
protected WindowAndroid(Context context, DisplayAndroid display, boolean trackOcclusion) {
mLifetimeAssert = LifetimeAssert.create(this);
// context does not have the same lifetime guarantees as an application context so we can't
// hold a strong reference to it.
@ -234,6 +256,75 @@ public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidO
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
mOverlayTransformApiHelper = OverlayTransformApiHelper.create(this);
}
mTrackOcclusion = trackOcclusion;
if (mTrackOcclusion) {
var decorView = getDecorView();
assert decorView != null;
// If the decor view is already attached to the window the listener won't be called.
// In this case, the window token exists so we can register the occlusion observer.
if (decorView.isAttachedToWindow()) {
maybeRegisterOcclusionObserver(getWindowToken());
}
decorView.addOnAttachStateChangeListener(this);
}
}
@Override
public void onViewAttachedToWindow(View v) {
maybeRegisterOcclusionObserver(v.getWindowToken());
}
@Override
public void onViewDetachedFromWindow(View v) {
maybeUnregisterOcclusionObserver();
}
private void maybeRegisterOcclusionObserver(IBinder windowToken) {
if (!mTrackOcclusion || Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
return;
}
assert mOcclusionObserver == null;
Context context = getContext().get();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
var thresholds = new TrustedPresentationThresholds(Float.MIN_VALUE, Float.MIN_VALUE, 1);
mOcclusionObserver =
new Consumer<Boolean>() {
@Override
public void accept(Boolean visible) {
mOcclusionSupplier.set(!visible);
}
};
assert windowToken != null;
wm.registerTrustedPresentationListener(
windowToken,
thresholds,
(r) -> {
PostTask.postTask(TaskTraits.UI_DEFAULT, r);
},
mOcclusionObserver);
}
private void maybeUnregisterOcclusionObserver() {
if (!mTrackOcclusion || Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
return;
}
assert mOcclusionObserver != null;
Context context = getContext().get();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.unregisterTrustedPresentationListener(mOcclusionObserver);
mOcclusionObserver = null;
}
/** A supplier that returns whether the window is occluded or not. */
public ObservableSupplier<Boolean> getOcclusionSupplier() {
return mOcclusionSupplier;
}
private static boolean isTv(Context context) {
@ -245,7 +336,9 @@ public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidO
@CalledByNativeForTesting
private static long createForTesting() {
WindowAndroid windowAndroid = new WindowAndroid(ContextUtils.getApplicationContext());
WindowAndroid windowAndroid =
new WindowAndroid(
ContextUtils.getApplicationContext(), /* trackOcclusion= */ false);
// |windowAndroid.getNativePointer()| creates native WindowAndroid object
// which stores a global ref to |windowAndroid|. Therefore |windowAndroid|
// is not immediately eligible for gc.
@ -873,6 +966,13 @@ public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidO
return mContextRef;
}
/** Return the decor view, or null. */
private View getDecorView() {
Window window = getWindow();
if (window == null) return null;
return window.getDecorView();
}
/** Return the current window token, or null. */
public IBinder getWindowToken() {
Window window = getWindow();