PageRenderTime 548ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreview.java

http://github.com/chromium/chromium
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
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. package org.chromium.chrome.browser.paint_preview;
  5. import android.animation.Animator;
  6. import android.animation.AnimatorListenerAdapter;
  7. import android.app.Activity;
  8. import android.graphics.Point;
  9. import android.os.Handler;
  10. import android.os.SystemClock;
  11. import android.view.View;
  12. import androidx.annotation.NonNull;
  13. import androidx.annotation.Nullable;
  14. import androidx.annotation.VisibleForTesting;
  15. import org.chromium.base.Callback;
  16. import org.chromium.base.TraceEvent;
  17. import org.chromium.base.UserData;
  18. import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
  19. import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabService;
  20. import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabServiceFactory;
  21. import org.chromium.chrome.browser.tab.EmptyTabObserver;
  22. import org.chromium.chrome.browser.tab.Tab;
  23. import org.chromium.chrome.browser.tab.TabHidingType;
  24. import org.chromium.chrome.browser.tab.TabObserver;
  25. import org.chromium.chrome.browser.tab.TabViewProvider;
  26. import org.chromium.components.browser_ui.styles.ChromeColors;
  27. import org.chromium.components.paintpreview.player.PlayerManager;
  28. import org.chromium.content_public.browser.RenderCoordinates;
  29. import org.chromium.content_public.browser.WebContents;
  30. import org.chromium.content_public.browser.WebContentsAccessibility;
  31. import org.chromium.ui.base.EventForwarder;
  32. import org.chromium.ui.base.GestureEventType;
  33. import org.chromium.ui.base.WindowAndroid;
  34. import org.chromium.ui.util.TokenHolder;
  35. /**
  36. * Responsible for checking for and displaying Paint Previews that are associated with a
  37. * {@link Tab} by overlaying the content view.
  38. */
  39. public class TabbedPaintPreview implements UserData {
  40. public static final Class<TabbedPaintPreview> USER_DATA_KEY = TabbedPaintPreview.class;
  41. private static final int CROSS_FADE_DURATION_MS = 500;
  42. private static final int SCROLL_DELAY_MS = 10;
  43. private Tab mTab;
  44. private TabObserver mTabObserver;
  45. private TabViewProvider mTabbedPaintPreviewViewProvider;
  46. private PaintPreviewTabService mPaintPreviewTabService;
  47. private PlayerManager mPlayerManager;
  48. private BrowserStateBrowserControlsVisibilityDelegate mBrowserVisibilityDelegate;
  49. private Runnable mProgressSimulatorNeededCallback;
  50. private Callback<Boolean> mProgressPreventionCallback;
  51. private boolean mIsAttachedToTab;
  52. private boolean mFadingOut;
  53. private int mPersistentToolbarToken = TokenHolder.INVALID_TOKEN;
  54. private static PaintPreviewTabService sPaintPreviewTabServiceForTesting;
  55. private boolean mWasEverShown;
  56. public static TabbedPaintPreview get(Tab tab) {
  57. if (tab.getUserDataHost().getUserData(USER_DATA_KEY) == null) {
  58. tab.getUserDataHost().setUserData(USER_DATA_KEY, new TabbedPaintPreview(tab));
  59. }
  60. return tab.getUserDataHost().getUserData(USER_DATA_KEY);
  61. }
  62. private TabbedPaintPreview(Tab tab) {
  63. mTab = tab;
  64. mTabbedPaintPreviewViewProvider = new TabbedPaintPreviewViewProvider();
  65. mPaintPreviewTabService = PaintPreviewTabServiceFactory.getServiceInstance();
  66. mTabObserver = new EmptyTabObserver() {
  67. @Override
  68. public void onHidden(Tab tab, @TabHidingType int hidingType) {
  69. releasePersistentToolbar();
  70. setProgressPreventionNeeded(false);
  71. }
  72. @Override
  73. public void onShown(Tab tab, int type) {
  74. if (!isShowing()) return;
  75. showToolbarPersistent();
  76. setProgressPreventionNeeded(true);
  77. }
  78. @Override
  79. public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
  80. // Intentionally do nothing to prevent automatic observer removal on detachment.
  81. }
  82. };
  83. }
  84. public void setBrowserVisibilityDelegate(
  85. BrowserStateBrowserControlsVisibilityDelegate browserVisibilityDelegate) {
  86. mBrowserVisibilityDelegate = browserVisibilityDelegate;
  87. }
  88. public void setProgressSimulatorNeededCallback(Runnable callback) {
  89. mProgressSimulatorNeededCallback = callback;
  90. }
  91. public void setProgressbarUpdatePreventionCallback(Callback<Boolean> callback) {
  92. mProgressPreventionCallback = callback;
  93. }
  94. void capture(Callback<Boolean> successCallback) {
  95. getService().captureTab(mTab, successCallback);
  96. }
  97. /**
  98. * Shows a Paint Preview for the provided tab if it exists.
  99. * @param listener An interface used for notifying events originated from the player.
  100. * @return Whether a capture for this tab exists and an attempt for displaying it has started.
  101. */
  102. public boolean maybeShow(@NonNull PlayerManager.Listener listener) {
  103. if (mIsAttachedToTab) return true;
  104. TraceEvent.begin("TabbedPaintPreview.maybeShow");
  105. // Check if a capture exists. This is a quick check using a cache.
  106. boolean hasCapture = getService().hasCaptureForTab(mTab.getId());
  107. if (!hasCapture) {
  108. TraceEvent.end("TabbedPaintPreview.maybeShow");
  109. return false;
  110. }
  111. mTab.addObserver(mTabObserver);
  112. PaintPreviewCompositorUtils.warmupCompositor();
  113. mPlayerManager = new PlayerManager(mTab.getUrl(), mTab.getContext(), getService(),
  114. String.valueOf(mTab.getId()), listener,
  115. ChromeColors.getPrimaryBackgroundColor(mTab.getContext(), false),
  116. /*ignoreInitialScrollOffset=*/false);
  117. // TODO(crbug/1230021): Consider deferring/post tasking. Locally this appears to be slow.
  118. TraceEvent.begin("TabbedPaintPreview.maybeShow addTabViewProvider");
  119. mTab.getTabViewManager().addTabViewProvider(mTabbedPaintPreviewViewProvider);
  120. TraceEvent.end("TabbedPaintPreview.maybeShow addTabViewProvider");
  121. mIsAttachedToTab = true;
  122. mWasEverShown = true;
  123. TraceEvent.end("TabbedPaintPreview.maybeShow");
  124. return true;
  125. }
  126. public void remove(boolean animate) {
  127. remove(true, animate);
  128. }
  129. private void matchScrollAndScale(
  130. WebContents contents, Point scrollPosition, float scaleFactor) {
  131. if (contents == null || scaleFactor == 0f || scrollPosition == null) return;
  132. EventForwarder eventForwarder = contents.getEventForwarder();
  133. RenderCoordinates coordinates = RenderCoordinates.fromWebContents(contents);
  134. float scaleDelta = scaleFactor / coordinates.getPageScaleFactor();
  135. long timeMs = SystemClock.uptimeMillis();
  136. eventForwarder.onGestureEvent(GestureEventType.PINCH_BEGIN, timeMs, 0.f);
  137. eventForwarder.onGestureEvent(GestureEventType.PINCH_BY, timeMs, scaleDelta);
  138. eventForwarder.onGestureEvent(GestureEventType.PINCH_END, timeMs, 0.f);
  139. // Post the scroll so it occurs after the scale. This ensures positioning is correct.
  140. new Handler().postDelayed(() -> {
  141. eventForwarder.scrollTo(scrollPosition.x, scrollPosition.y);
  142. }, SCROLL_DELAY_MS);
  143. }
  144. /**
  145. * Removes the view containing the Paint Preview from the most recently shown {@link Tab}. Does
  146. * nothing if there is no view showing.
  147. */
  148. public void remove(boolean matchScroll, boolean animate) {
  149. PaintPreviewCompositorUtils.stopWarmCompositor();
  150. if (mTab == null || mPlayerManager == null || mFadingOut) return;
  151. TraceEvent.begin("TabbedPaintPreview.remove");
  152. mFadingOut = true;
  153. mPlayerManager.setAcceptUserInput(false);
  154. mTab.removeObserver(mTabObserver);
  155. Point scrollPosition = mPlayerManager.getScrollPosition();
  156. float scale = mPlayerManager.getScale();
  157. final boolean supportsAccessibility = mPlayerManager.supportsAccessibility();
  158. // Destroy early to free up resource, but don't null until faded out so view sticks around.
  159. mPlayerManager.destroy();
  160. if (matchScroll) {
  161. matchScrollAndScale(mTab.getWebContents(), scrollPosition, scale);
  162. }
  163. mTabbedPaintPreviewViewProvider.getView()
  164. .animate()
  165. .alpha(0f)
  166. .setDuration(animate ? CROSS_FADE_DURATION_MS : 0)
  167. .setListener(new AnimatorListenerAdapter() {
  168. @Override
  169. public void onAnimationEnd(Animator animation) {
  170. if (mTab != null) {
  171. mTab.getTabViewManager().removeTabViewProvider(
  172. mTabbedPaintPreviewViewProvider);
  173. }
  174. if (mPlayerManager != null) {
  175. mPlayerManager = null;
  176. }
  177. // WebContentsAccessibilityImpl gets its focus stuck on the root ID. Clear
  178. // focus here to solve this problem.
  179. if (supportsAccessibility) clearFocus();
  180. mIsAttachedToTab = false;
  181. mFadingOut = false;
  182. }
  183. });
  184. if (mProgressSimulatorNeededCallback != null) mProgressSimulatorNeededCallback.run();
  185. TraceEvent.end("TabbedPaintPreview.remove");
  186. }
  187. /**
  188. * Clears focus and accessibility focus.
  189. */
  190. private void clearFocus() {
  191. WebContents webContents = mTab != null ? mTab.getWebContents() : null;
  192. if (webContents == null || webContents.isDestroyed()) return;
  193. // Clear input focus. This is required due to a bug where the root view is treated as
  194. // focused for input on exit causing talkback to attempt to return focus to the root view.
  195. // TODO(crbug/1197693): this approach could cause loss of focus in a menu, omnibox, etc.
  196. // is there a less heavy-handed option here?
  197. WindowAndroid window = webContents.getTopLevelNativeWindow();
  198. Activity activity = window != null ? window.getActivity().get() : null;
  199. View v = activity != null ? activity.getCurrentFocus() : null;
  200. if (v != null) v.clearFocus();
  201. // Clear accessibility focus.
  202. WebContentsAccessibility wcax = WebContentsAccessibility.fromWebContents(webContents);
  203. if (wcax != null) wcax.resetFocus();
  204. }
  205. public boolean isShowing() {
  206. if (mTab == null) return false;
  207. return mTab.getTabViewManager().isShowing(mTabbedPaintPreviewViewProvider);
  208. }
  209. public boolean isAttached() {
  210. return mIsAttachedToTab;
  211. }
  212. /**
  213. * Persistently shows the toolbar and avoids hiding it on scrolling down.
  214. */
  215. private void showToolbarPersistent() {
  216. if (mBrowserVisibilityDelegate == null
  217. || mPersistentToolbarToken != TokenHolder.INVALID_TOKEN) {
  218. return;
  219. }
  220. mPersistentToolbarToken = mBrowserVisibilityDelegate.showControlsPersistent();
  221. }
  222. private void releasePersistentToolbar() {
  223. if (mBrowserVisibilityDelegate == null) return;
  224. mBrowserVisibilityDelegate.releasePersistentShowingToken(mPersistentToolbarToken);
  225. mPersistentToolbarToken = TokenHolder.INVALID_TOKEN;
  226. }
  227. /**
  228. * @param progressPrevention Whether progress updates shown in the progress bar should be
  229. * suppressed.
  230. */
  231. private void setProgressPreventionNeeded(boolean progressPrevention) {
  232. if (mProgressPreventionCallback == null) return;
  233. mProgressPreventionCallback.onResult(progressPrevention);
  234. }
  235. @Override
  236. public void destroy() {
  237. mTab.removeObserver(mTabObserver);
  238. mTab = null;
  239. }
  240. private PaintPreviewTabService getService() {
  241. if (sPaintPreviewTabServiceForTesting == null) return mPaintPreviewTabService;
  242. return sPaintPreviewTabServiceForTesting;
  243. }
  244. @VisibleForTesting
  245. static void overridePaintPreviewTabServiceForTesting(PaintPreviewTabService service) {
  246. sPaintPreviewTabServiceForTesting = service;
  247. }
  248. @VisibleForTesting
  249. boolean wasEverShown() {
  250. return mWasEverShown;
  251. }
  252. @VisibleForTesting
  253. View getViewForTesting() {
  254. return mTabbedPaintPreviewViewProvider.getView();
  255. }
  256. @VisibleForTesting
  257. PlayerManager getPlayerManagerForTesting() {
  258. return mPlayerManager;
  259. }
  260. private class TabbedPaintPreviewViewProvider implements TabViewProvider {
  261. @Override
  262. public int getTabViewProviderType() {
  263. return Type.PAINT_PREVIEW;
  264. }
  265. @Override
  266. public View getView() {
  267. return mPlayerManager == null ? null : mPlayerManager.getView();
  268. }
  269. @Override
  270. public void onShown() {
  271. showToolbarPersistent();
  272. setProgressPreventionNeeded(true);
  273. }
  274. @Override
  275. public void onHidden() {
  276. releasePersistentToolbar();
  277. setProgressPreventionNeeded(false);
  278. }
  279. }
  280. }