| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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 | |
| 5 package org.chromium.chrome.browser.compositor.bottombar.readermode; | |
| 6 | |
| 7 import android.app.Activity; | |
| 8 import android.content.Context; | |
| 9 import android.graphics.RectF; | |
| 10 | |
| 11 import org.chromium.base.ActivityState; | |
| 12 import org.chromium.chrome.R; | |
| 13 import org.chromium.chrome.browser.compositor.LayerTitleCache; | |
| 14 import org.chromium.chrome.browser.compositor.bottombar.OverlayContentDelegate; | |
| 15 import org.chromium.chrome.browser.compositor.bottombar.OverlayContentProgressOb
server; | |
| 16 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel; | |
| 17 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChange
Reason; | |
| 18 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContent; | |
| 19 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContentViewD
elegate; | |
| 20 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager; | |
| 21 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.Pane
lPriority; | |
| 22 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; | |
| 23 import org.chromium.chrome.browser.compositor.scene_layer.ReaderModeSceneLayer; | |
| 24 import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer; | |
| 25 import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils; | |
| 26 import org.chromium.chrome.browser.dom_distiller.ReaderModeManagerDelegate; | |
| 27 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler; | |
| 28 import org.chromium.chrome.browser.rappor.RapporServiceBridge; | |
| 29 import org.chromium.components.navigation_interception.NavigationParams; | |
| 30 import org.chromium.content.browser.ContentViewCore; | |
| 31 import org.chromium.content_public.browser.WebContents; | |
| 32 import org.chromium.ui.resources.ResourceManager; | |
| 33 | |
| 34 /** | |
| 35 * The panel containing reader mode. | |
| 36 */ | |
| 37 public class ReaderModePanel extends OverlayPanel { | |
| 38 | |
| 39 /** The compositor layer used for drawing the panel. */ | |
| 40 private ReaderModeSceneLayer mSceneLayer; | |
| 41 | |
| 42 /** Delegate for calling functions on the ReaderModeManager. */ | |
| 43 private ReaderModeManagerDelegate mManagerDelegate; | |
| 44 | |
| 45 /** Delegate for passing the current ContentViewCore to the layout manager.
*/ | |
| 46 private OverlayPanelContentViewDelegate mContentViewDelegate; | |
| 47 | |
| 48 /** The opacity of the panel bar text. */ | |
| 49 private float mReaderBarTextOpacity; | |
| 50 | |
| 51 /** If the timer for counting how long a user has been reading is running. *
/ | |
| 52 private boolean mTimerRunning; | |
| 53 | |
| 54 /** The start time in ms of the current timer. */ | |
| 55 private long mStartTime; | |
| 56 | |
| 57 // =========================================================================
=================== | |
| 58 // Constructor | |
| 59 // =========================================================================
=================== | |
| 60 | |
| 61 /** | |
| 62 * @param context The current Android {@link Context}. | |
| 63 * @param updateHost The {@link LayoutUpdateHost} used to request updates in
the Layout. | |
| 64 * @param panelManager The {@link OverlayPanelManager} used to control panel
show/hide. | |
| 65 * @param contentViewDelegate Notifies the activity that a ContentViewCore h
as been created. | |
| 66 */ | |
| 67 public ReaderModePanel(Context context, LayoutUpdateHost updateHost, | |
| 68 OverlayPanelManager panelManager, OverlayPanelContentViewDelegate co
ntentViewDelegate) { | |
| 69 super(context, updateHost, panelManager); | |
| 70 mSceneLayer = createNewReaderModeSceneLayer(); | |
| 71 mContentViewDelegate = contentViewDelegate; | |
| 72 } | |
| 73 | |
| 74 @Override | |
| 75 public OverlayPanelContent createNewOverlayPanelContent() { | |
| 76 OverlayContentDelegate delegate = new OverlayContentDelegate() { | |
| 77 /** | |
| 78 * Track if a navigation/load is the first one for this content. | |
| 79 */ | |
| 80 private boolean mIsInitialLoad = true; | |
| 81 | |
| 82 @Override | |
| 83 public void onContentViewCreated(ContentViewCore contentView) { | |
| 84 mContentViewDelegate.setOverlayPanelContentViewCore(contentView)
; | |
| 85 | |
| 86 WebContents distilledWebContents = contentView.getWebContents(); | |
| 87 if (distilledWebContents == null) { | |
| 88 closePanel(StateChangeReason.UNKNOWN, false); | |
| 89 return; | |
| 90 } | |
| 91 | |
| 92 WebContents sourceWebContents = mManagerDelegate.getBasePageWebC
ontents(); | |
| 93 if (sourceWebContents == null) { | |
| 94 closePanel(StateChangeReason.UNKNOWN, false); | |
| 95 return; | |
| 96 } | |
| 97 | |
| 98 DomDistillerTabUtils.distillAndView(sourceWebContents, distilled
WebContents); | |
| 99 } | |
| 100 | |
| 101 @Override | |
| 102 public void onContentViewDestroyed() { | |
| 103 mContentViewDelegate.releaseOverlayPanelContentViewCore(); | |
| 104 mIsInitialLoad = true; | |
| 105 } | |
| 106 | |
| 107 @Override | |
| 108 public boolean shouldInterceptNavigation(ExternalNavigationHandler e
xternalNavHandler, | |
| 109 NavigationParams navigationParams) { | |
| 110 // The initial load will be the distilled content; don't try to
open a new tab if | |
| 111 // this is the case. All other navigations on distilled pages wi
ll come from link | |
| 112 // clicks. | |
| 113 if (mIsInitialLoad) { | |
| 114 mIsInitialLoad = false; | |
| 115 return true; | |
| 116 } | |
| 117 if (!navigationParams.isExternalProtocol) { | |
| 118 mManagerDelegate.createNewTab(navigationParams.url); | |
| 119 return false; | |
| 120 } | |
| 121 return true; | |
| 122 } | |
| 123 }; | |
| 124 | |
| 125 return new OverlayPanelContent(delegate, new OverlayContentProgressObser
ver(), mActivity); | |
| 126 } | |
| 127 | |
| 128 // =========================================================================
=================== | |
| 129 // Scene Overlay | |
| 130 // =========================================================================
=================== | |
| 131 | |
| 132 /** | |
| 133 * Create a new scene layer for this panel. This should be overridden by tes
ts as necessary. | |
| 134 */ | |
| 135 protected ReaderModeSceneLayer createNewReaderModeSceneLayer() { | |
| 136 return new ReaderModeSceneLayer(mContext.getResources().getDisplayMetric
s().density); | |
| 137 } | |
| 138 | |
| 139 @Override | |
| 140 public SceneOverlayLayer getUpdatedSceneOverlayTree(RectF viewport, RectF vi
sibleViewport, | |
| 141 LayerTitleCache layerTitleCache, ResourceManager resourceManager, fl
oat yOffset) { | |
| 142 mSceneLayer.update(resourceManager, this, getBarTextViewId(), mReaderBar
TextOpacity); | |
| 143 | |
| 144 return mSceneLayer; | |
| 145 } | |
| 146 | |
| 147 @Override | |
| 148 public boolean updateOverlay(long time, long dt) { | |
| 149 // This will cause the ContentViewCore to size itself appropriately for
the panel (includes | |
| 150 // browser controls height). | |
| 151 updateBrowserControlsState(); | |
| 152 | |
| 153 return super.updateOverlay(time, dt); | |
| 154 } | |
| 155 | |
| 156 // =========================================================================
=================== | |
| 157 // Manager Integration | |
| 158 // =========================================================================
=================== | |
| 159 | |
| 160 /** | |
| 161 * Sets the {@code ReaderModeManagerDelegate} associated with this panel. | |
| 162 * @param delegate The {@code ReaderModeManagerDelegate}. | |
| 163 */ | |
| 164 public void setManagerDelegate(ReaderModeManagerDelegate delegate) { | |
| 165 // TODO(mdjones): This looks similar to setManagementDelegate in Context
ualSearchPanel, | |
| 166 // consider moving this to OverlayPanel. | |
| 167 if (mManagerDelegate != delegate) { | |
| 168 mManagerDelegate = delegate; | |
| 169 if (mManagerDelegate != null) { | |
| 170 setChromeActivity(mManagerDelegate.getChromeActivity()); | |
| 171 } | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 // =========================================================================
=================== | |
| 176 // Generic Event Handling | |
| 177 // =========================================================================
=================== | |
| 178 | |
| 179 @Override | |
| 180 public void handleBarClick(long time, float x, float y) { | |
| 181 super.handleBarClick(time, x, y); | |
| 182 if (isCoordinateInsideCloseButton(x)) { | |
| 183 closePanel(StateChangeReason.CLOSE_BUTTON, true); | |
| 184 } else { | |
| 185 maximizePanel(StateChangeReason.SEARCH_BAR_TAP); | |
| 186 } | |
| 187 } | |
| 188 | |
| 189 @Override | |
| 190 public boolean onInterceptBarClick() { | |
| 191 // TODO(mdjones): Handle compatibility mode here (promote to tab on tap)
. | |
| 192 return false; | |
| 193 } | |
| 194 | |
| 195 // =========================================================================
=================== | |
| 196 // Panel base methods | |
| 197 // =========================================================================
=================== | |
| 198 | |
| 199 @Override | |
| 200 public void destroyComponents() { | |
| 201 super.destroyComponents(); | |
| 202 destroyReaderModeBarControl(); | |
| 203 } | |
| 204 | |
| 205 @Override | |
| 206 public PanelPriority getPriority() { | |
| 207 return PanelPriority.MEDIUM; | |
| 208 } | |
| 209 | |
| 210 @Override | |
| 211 public boolean canBeSuppressed() { | |
| 212 return true; | |
| 213 } | |
| 214 | |
| 215 @Override | |
| 216 protected boolean isSupportedState(PanelState state) { | |
| 217 return state != PanelState.EXPANDED; | |
| 218 } | |
| 219 | |
| 220 @Override | |
| 221 protected float getThresholdToNextState() { | |
| 222 return 0.30f; | |
| 223 } | |
| 224 | |
| 225 @Override | |
| 226 protected void updatePanelForCloseOrPeek(float percent) { | |
| 227 super.updatePanelForCloseOrPeek(percent); | |
| 228 | |
| 229 // Do not update the panel text if the panel was closed immediately. | |
| 230 if (percent < 0.01f) return; | |
| 231 | |
| 232 getReaderModeBarControl().setBarText(R.string.reader_view_text); | |
| 233 mReaderBarTextOpacity = 1.0f; | |
| 234 } | |
| 235 | |
| 236 @Override | |
| 237 protected void updatePanelForMaximization(float percent) { | |
| 238 super.updatePanelForMaximization(percent); | |
| 239 if (percent < 0.5f) { | |
| 240 mReaderBarTextOpacity = 1.0f - 2.0f * percent; | |
| 241 getReaderModeBarControl().setBarText(R.string.reader_view_text); | |
| 242 } else { | |
| 243 mReaderBarTextOpacity = 2.0f * (percent - 0.5f); | |
| 244 getReaderModeBarControl().setBarText(R.string.reader_mode_maximized_
title); | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 @Override | |
| 249 protected void maximizePanel(StateChangeReason reason) { | |
| 250 // Extend animation time by 150ms. | |
| 251 super.animatePanelToState(PanelState.MAXIMIZED, reason, BASE_ANIMATION_D
URATION_MS + 150); | |
| 252 } | |
| 253 | |
| 254 @Override | |
| 255 protected void onAnimationFinished() { | |
| 256 super.onAnimationFinished(); | |
| 257 boolean animatingToOpenState = getPanelState() == PanelState.MAXIMIZED; | |
| 258 // Start or stop the timer for how long the user has been reading. | |
| 259 if (!mTimerRunning && animatingToOpenState) { | |
| 260 mStartTime = System.currentTimeMillis(); | |
| 261 mTimerRunning = true; | |
| 262 if (mManagerDelegate != null && mManagerDelegate.getBasePageWebConte
nts() != null) { | |
| 263 String url = mManagerDelegate.getBasePageWebContents().getUrl(); | |
| 264 RapporServiceBridge.sampleDomainAndRegistryFromURL( | |
| 265 "DomDistiller.OpenPanel", url); | |
| 266 } | |
| 267 } else if (mTimerRunning && !animatingToOpenState) { | |
| 268 onTimerEnded(); | |
| 269 } | |
| 270 } | |
| 271 | |
| 272 @Override | |
| 273 public void peekPanel(StateChangeReason reason) { | |
| 274 super.peekPanel(reason); | |
| 275 if (mManagerDelegate == null) return; | |
| 276 mManagerDelegate.onPanelShown(); | |
| 277 } | |
| 278 | |
| 279 @Override | |
| 280 public void closePanel(StateChangeReason reason, boolean animate) { | |
| 281 super.closePanel(reason, animate); | |
| 282 if (mTimerRunning) { | |
| 283 onTimerEnded(); | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 @Override | |
| 288 protected void onClosed(StateChangeReason reason) { | |
| 289 super.onClosed(reason); | |
| 290 if (mSceneLayer != null) mSceneLayer.hideTree(); | |
| 291 if (mManagerDelegate == null) return; | |
| 292 mManagerDelegate.onClosed(reason); | |
| 293 } | |
| 294 | |
| 295 /** | |
| 296 * Record the time spent in Reader Mode. | |
| 297 */ | |
| 298 private void onTimerEnded() { | |
| 299 mTimerRunning = false; | |
| 300 long totalTime = System.currentTimeMillis() - mStartTime; | |
| 301 if (mStartTime <= 0 || totalTime < 0) return; | |
| 302 mManagerDelegate.recordTimeSpentInReader(totalTime); | |
| 303 } | |
| 304 | |
| 305 @Override | |
| 306 public float getOffsetY() { | |
| 307 // Do not attempt to auto-hide the reader mode bar if the toolbar is les
s than a certain | |
| 308 // height. | |
| 309 boolean shouldAutoHide = getToolbarHeight() >= getBarHeightPeeking(); | |
| 310 // This will cause the reader mode bar to behave like the browser contro
ls; sliding out of | |
| 311 // view as the page scrolls. | |
| 312 return super.getOffsetY() + (shouldAutoHide ? getBrowserControlsOffsetDp
() : 0.0f); | |
| 313 } | |
| 314 | |
| 315 @Override | |
| 316 public void onLayoutChanged(float width, float height, float visibleViewport
OffsetY) { | |
| 317 if (width != getWidth()) destroyReaderModeBarControl(); | |
| 318 | |
| 319 super.onLayoutChanged(width, height, visibleViewportOffsetY); | |
| 320 } | |
| 321 | |
| 322 @Override | |
| 323 protected float calculateBasePageDesiredOffset() { | |
| 324 return -getToolbarHeight(); | |
| 325 } | |
| 326 | |
| 327 @Override | |
| 328 public void onActivityStateChange(Activity activity, int newState) { | |
| 329 // If the activity is only resuming, don't do anything. | |
| 330 if (newState == ActivityState.RESUMED) return; | |
| 331 super.onActivityStateChange(activity, newState); | |
| 332 } | |
| 333 | |
| 334 // =========================================================================
=================== | |
| 335 // ReaderModeBarControl | |
| 336 // =========================================================================
=================== | |
| 337 | |
| 338 private ReaderModeBarControl mReaderModeBarControl; | |
| 339 | |
| 340 /** | |
| 341 * @return The Id of the Search Term View. | |
| 342 */ | |
| 343 public int getBarTextViewId() { | |
| 344 return getReaderModeBarControl().getViewId(); | |
| 345 } | |
| 346 | |
| 347 /** | |
| 348 * Creates the ReaderModeBarControl, if needed. The Views are set to INVISIB
LE, because | |
| 349 * they won't actually be displayed on the screen (their snapshots will be d
isplayed instead). | |
| 350 */ | |
| 351 protected ReaderModeBarControl getReaderModeBarControl() { | |
| 352 assert mContainerView != null; | |
| 353 assert mResourceLoader != null; | |
| 354 | |
| 355 if (mReaderModeBarControl == null) { | |
| 356 mReaderModeBarControl = | |
| 357 new ReaderModeBarControl(this, mContext, mContainerView, mRe
sourceLoader); | |
| 358 } | |
| 359 | |
| 360 assert mReaderModeBarControl != null; | |
| 361 return mReaderModeBarControl; | |
| 362 } | |
| 363 | |
| 364 /** | |
| 365 * Destroys the ReaderModeBarControl. | |
| 366 */ | |
| 367 protected void destroyReaderModeBarControl() { | |
| 368 if (mReaderModeBarControl != null) { | |
| 369 mReaderModeBarControl.destroy(); | |
| 370 mReaderModeBarControl = null; | |
| 371 } | |
| 372 } | |
| 373 } | |
| OLD | NEW |