/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreview.java
Java | 333 lines | 242 code | 53 blank | 38 comment | 47 complexity | d0cde9de4495c62e71ea28a0442503a1 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.0, BSD-2-Clause, LGPL-2.1, MPL-2.0, 0BSD, EPL-1.0, MPL-2.0-no-copyleft-exception, GPL-2.0, BitTorrent-1.0, CPL-1.0, LGPL-3.0, Unlicense, BSD-3-Clause, CC0-1.0, JSON, MIT, GPL-3.0, CC-BY-SA-3.0, AGPL-1.0
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- package org.chromium.chrome.browser.paint_preview;
- import android.animation.Animator;
- import android.animation.AnimatorListenerAdapter;
- import android.app.Activity;
- import android.graphics.Point;
- import android.os.Handler;
- import android.os.SystemClock;
- import android.view.View;
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
- import androidx.annotation.VisibleForTesting;
- import org.chromium.base.Callback;
- import org.chromium.base.TraceEvent;
- import org.chromium.base.UserData;
- import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
- import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabService;
- import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabServiceFactory;
- import org.chromium.chrome.browser.tab.EmptyTabObserver;
- import org.chromium.chrome.browser.tab.Tab;
- import org.chromium.chrome.browser.tab.TabHidingType;
- import org.chromium.chrome.browser.tab.TabObserver;
- import org.chromium.chrome.browser.tab.TabViewProvider;
- import org.chromium.components.browser_ui.styles.ChromeColors;
- import org.chromium.components.paintpreview.player.PlayerManager;
- import org.chromium.content_public.browser.RenderCoordinates;
- import org.chromium.content_public.browser.WebContents;
- import org.chromium.content_public.browser.WebContentsAccessibility;
- import org.chromium.ui.base.EventForwarder;
- import org.chromium.ui.base.GestureEventType;
- import org.chromium.ui.base.WindowAndroid;
- import org.chromium.ui.util.TokenHolder;
- /**
- * Responsible for checking for and displaying Paint Previews that are associated with a
- * {@link Tab} by overlaying the content view.
- */
- public class TabbedPaintPreview implements UserData {
- public static final Class<TabbedPaintPreview> USER_DATA_KEY = TabbedPaintPreview.class;
- private static final int CROSS_FADE_DURATION_MS = 500;
- private static final int SCROLL_DELAY_MS = 10;
- private Tab mTab;
- private TabObserver mTabObserver;
- private TabViewProvider mTabbedPaintPreviewViewProvider;
- private PaintPreviewTabService mPaintPreviewTabService;
- private PlayerManager mPlayerManager;
- private BrowserStateBrowserControlsVisibilityDelegate mBrowserVisibilityDelegate;
- private Runnable mProgressSimulatorNeededCallback;
- private Callback<Boolean> mProgressPreventionCallback;
- private boolean mIsAttachedToTab;
- private boolean mFadingOut;
- private int mPersistentToolbarToken = TokenHolder.INVALID_TOKEN;
- private static PaintPreviewTabService sPaintPreviewTabServiceForTesting;
- private boolean mWasEverShown;
- public static TabbedPaintPreview get(Tab tab) {
- if (tab.getUserDataHost().getUserData(USER_DATA_KEY) == null) {
- tab.getUserDataHost().setUserData(USER_DATA_KEY, new TabbedPaintPreview(tab));
- }
- return tab.getUserDataHost().getUserData(USER_DATA_KEY);
- }
- private TabbedPaintPreview(Tab tab) {
- mTab = tab;
- mTabbedPaintPreviewViewProvider = new TabbedPaintPreviewViewProvider();
- mPaintPreviewTabService = PaintPreviewTabServiceFactory.getServiceInstance();
- mTabObserver = new EmptyTabObserver() {
- @Override
- public void onHidden(Tab tab, @TabHidingType int hidingType) {
- releasePersistentToolbar();
- setProgressPreventionNeeded(false);
- }
- @Override
- public void onShown(Tab tab, int type) {
- if (!isShowing()) return;
- showToolbarPersistent();
- setProgressPreventionNeeded(true);
- }
- @Override
- public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
- // Intentionally do nothing to prevent automatic observer removal on detachment.
- }
- };
- }
- public void setBrowserVisibilityDelegate(
- BrowserStateBrowserControlsVisibilityDelegate browserVisibilityDelegate) {
- mBrowserVisibilityDelegate = browserVisibilityDelegate;
- }
- public void setProgressSimulatorNeededCallback(Runnable callback) {
- mProgressSimulatorNeededCallback = callback;
- }
- public void setProgressbarUpdatePreventionCallback(Callback<Boolean> callback) {
- mProgressPreventionCallback = callback;
- }
- void capture(Callback<Boolean> successCallback) {
- getService().captureTab(mTab, successCallback);
- }
- /**
- * Shows a Paint Preview for the provided tab if it exists.
- * @param listener An interface used for notifying events originated from the player.
- * @return Whether a capture for this tab exists and an attempt for displaying it has started.
- */
- public boolean maybeShow(@NonNull PlayerManager.Listener listener) {
- if (mIsAttachedToTab) return true;
- TraceEvent.begin("TabbedPaintPreview.maybeShow");
- // Check if a capture exists. This is a quick check using a cache.
- boolean hasCapture = getService().hasCaptureForTab(mTab.getId());
- if (!hasCapture) {
- TraceEvent.end("TabbedPaintPreview.maybeShow");
- return false;
- }
- mTab.addObserver(mTabObserver);
- PaintPreviewCompositorUtils.warmupCompositor();
- mPlayerManager = new PlayerManager(mTab.getUrl(), mTab.getContext(), getService(),
- String.valueOf(mTab.getId()), listener,
- ChromeColors.getPrimaryBackgroundColor(mTab.getContext(), false),
- /*ignoreInitialScrollOffset=*/false);
- // TODO(crbug/1230021): Consider deferring/post tasking. Locally this appears to be slow.
- TraceEvent.begin("TabbedPaintPreview.maybeShow addTabViewProvider");
- mTab.getTabViewManager().addTabViewProvider(mTabbedPaintPreviewViewProvider);
- TraceEvent.end("TabbedPaintPreview.maybeShow addTabViewProvider");
- mIsAttachedToTab = true;
- mWasEverShown = true;
- TraceEvent.end("TabbedPaintPreview.maybeShow");
- return true;
- }
- public void remove(boolean animate) {
- remove(true, animate);
- }
- private void matchScrollAndScale(
- WebContents contents, Point scrollPosition, float scaleFactor) {
- if (contents == null || scaleFactor == 0f || scrollPosition == null) return;
- EventForwarder eventForwarder = contents.getEventForwarder();
- RenderCoordinates coordinates = RenderCoordinates.fromWebContents(contents);
- float scaleDelta = scaleFactor / coordinates.getPageScaleFactor();
- long timeMs = SystemClock.uptimeMillis();
- eventForwarder.onGestureEvent(GestureEventType.PINCH_BEGIN, timeMs, 0.f);
- eventForwarder.onGestureEvent(GestureEventType.PINCH_BY, timeMs, scaleDelta);
- eventForwarder.onGestureEvent(GestureEventType.PINCH_END, timeMs, 0.f);
- // Post the scroll so it occurs after the scale. This ensures positioning is correct.
- new Handler().postDelayed(() -> {
- eventForwarder.scrollTo(scrollPosition.x, scrollPosition.y);
- }, SCROLL_DELAY_MS);
- }
- /**
- * Removes the view containing the Paint Preview from the most recently shown {@link Tab}. Does
- * nothing if there is no view showing.
- */
- public void remove(boolean matchScroll, boolean animate) {
- PaintPreviewCompositorUtils.stopWarmCompositor();
- if (mTab == null || mPlayerManager == null || mFadingOut) return;
- TraceEvent.begin("TabbedPaintPreview.remove");
- mFadingOut = true;
- mPlayerManager.setAcceptUserInput(false);
- mTab.removeObserver(mTabObserver);
- Point scrollPosition = mPlayerManager.getScrollPosition();
- float scale = mPlayerManager.getScale();
- final boolean supportsAccessibility = mPlayerManager.supportsAccessibility();
- // Destroy early to free up resource, but don't null until faded out so view sticks around.
- mPlayerManager.destroy();
- if (matchScroll) {
- matchScrollAndScale(mTab.getWebContents(), scrollPosition, scale);
- }
- mTabbedPaintPreviewViewProvider.getView()
- .animate()
- .alpha(0f)
- .setDuration(animate ? CROSS_FADE_DURATION_MS : 0)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mTab != null) {
- mTab.getTabViewManager().removeTabViewProvider(
- mTabbedPaintPreviewViewProvider);
- }
- if (mPlayerManager != null) {
- mPlayerManager = null;
- }
- // WebContentsAccessibilityImpl gets its focus stuck on the root ID. Clear
- // focus here to solve this problem.
- if (supportsAccessibility) clearFocus();
- mIsAttachedToTab = false;
- mFadingOut = false;
- }
- });
- if (mProgressSimulatorNeededCallback != null) mProgressSimulatorNeededCallback.run();
- TraceEvent.end("TabbedPaintPreview.remove");
- }
- /**
- * Clears focus and accessibility focus.
- */
- private void clearFocus() {
- WebContents webContents = mTab != null ? mTab.getWebContents() : null;
- if (webContents == null || webContents.isDestroyed()) return;
- // Clear input focus. This is required due to a bug where the root view is treated as
- // focused for input on exit causing talkback to attempt to return focus to the root view.
- // TODO(crbug/1197693): this approach could cause loss of focus in a menu, omnibox, etc.
- // is there a less heavy-handed option here?
- WindowAndroid window = webContents.getTopLevelNativeWindow();
- Activity activity = window != null ? window.getActivity().get() : null;
- View v = activity != null ? activity.getCurrentFocus() : null;
- if (v != null) v.clearFocus();
- // Clear accessibility focus.
- WebContentsAccessibility wcax = WebContentsAccessibility.fromWebContents(webContents);
- if (wcax != null) wcax.resetFocus();
- }
- public boolean isShowing() {
- if (mTab == null) return false;
- return mTab.getTabViewManager().isShowing(mTabbedPaintPreviewViewProvider);
- }
- public boolean isAttached() {
- return mIsAttachedToTab;
- }
- /**
- * Persistently shows the toolbar and avoids hiding it on scrolling down.
- */
- private void showToolbarPersistent() {
- if (mBrowserVisibilityDelegate == null
- || mPersistentToolbarToken != TokenHolder.INVALID_TOKEN) {
- return;
- }
- mPersistentToolbarToken = mBrowserVisibilityDelegate.showControlsPersistent();
- }
- private void releasePersistentToolbar() {
- if (mBrowserVisibilityDelegate == null) return;
- mBrowserVisibilityDelegate.releasePersistentShowingToken(mPersistentToolbarToken);
- mPersistentToolbarToken = TokenHolder.INVALID_TOKEN;
- }
- /**
- * @param progressPrevention Whether progress updates shown in the progress bar should be
- * suppressed.
- */
- private void setProgressPreventionNeeded(boolean progressPrevention) {
- if (mProgressPreventionCallback == null) return;
- mProgressPreventionCallback.onResult(progressPrevention);
- }
- @Override
- public void destroy() {
- mTab.removeObserver(mTabObserver);
- mTab = null;
- }
- private PaintPreviewTabService getService() {
- if (sPaintPreviewTabServiceForTesting == null) return mPaintPreviewTabService;
- return sPaintPreviewTabServiceForTesting;
- }
- @VisibleForTesting
- static void overridePaintPreviewTabServiceForTesting(PaintPreviewTabService service) {
- sPaintPreviewTabServiceForTesting = service;
- }
- @VisibleForTesting
- boolean wasEverShown() {
- return mWasEverShown;
- }
- @VisibleForTesting
- View getViewForTesting() {
- return mTabbedPaintPreviewViewProvider.getView();
- }
- @VisibleForTesting
- PlayerManager getPlayerManagerForTesting() {
- return mPlayerManager;
- }
- private class TabbedPaintPreviewViewProvider implements TabViewProvider {
- @Override
- public int getTabViewProviderType() {
- return Type.PAINT_PREVIEW;
- }
- @Override
- public View getView() {
- return mPlayerManager == null ? null : mPlayerManager.getView();
- }
- @Override
- public void onShown() {
- showToolbarPersistent();
- setProgressPreventionNeeded(true);
- }
- @Override
- public void onHidden() {
- releasePersistentToolbar();
- setProgressPreventionNeeded(false);
- }
- }
- }