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.dom_distiller; | |
6 | |
7 import static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Ani
matableAnimation.createAnimation; | |
8 | |
9 import android.content.Context; | |
10 import android.os.SystemClock; | |
11 | |
12 import org.chromium.base.CommandLine; | |
13 import org.chromium.base.metrics.RecordHistogram; | |
14 import org.chromium.base.metrics.RecordUserAction; | |
15 import org.chromium.chrome.browser.ChromeSwitches; | |
16 import org.chromium.chrome.browser.WebContentsFactory; | |
17 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation; | |
18 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable
; | |
19 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEvent
Filter.ScrollDirection; | |
20 import org.chromium.chrome.browser.compositor.scene_layer.ReaderModeSceneLayer; | |
21 import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer; | |
22 import org.chromium.chrome.browser.dom_distiller.ReaderModeButtonView.ReaderMode
ButtonViewDelegate; | |
23 import org.chromium.chrome.browser.tab.EmptyTabObserver; | |
24 import org.chromium.chrome.browser.tab.Tab; | |
25 import org.chromium.chrome.browser.util.MathUtils; | |
26 import org.chromium.content.browser.ContentView; | |
27 import org.chromium.content.browser.ContentViewClient; | |
28 import org.chromium.content.browser.ContentViewCore; | |
29 import org.chromium.content_public.browser.NavigationController; | |
30 import org.chromium.content_public.browser.WebContents; | |
31 import org.chromium.content_public.browser.WebContentsObserver; | |
32 import org.chromium.ui.base.WindowAndroid; | |
33 import org.chromium.ui.resources.ResourceManager; | |
34 | |
35 import java.util.concurrent.TimeUnit; | |
36 | |
37 /** | |
38 * Manages UI effects for reader mode including hiding and showing the | |
39 * reader mode and reader mode preferences toolbar icon and hiding the | |
40 * top controls when a reader mode page has finished loading. | |
41 * | |
42 * TODO(aruslan): combine with ContextualSearchPanel. | |
43 */ | |
44 public class ReaderModePanel implements ChromeAnimation.Animatable<ReaderModePan
el.Property> { | |
45 // TODO(aruslan): pull this from the FullscreenManager. | |
46 private static final float TOOLBAR_HEIGHT_DP = 56.0f; | |
47 | |
48 private static final float PANEL_HEIGHT_DP = TOOLBAR_HEIGHT_DP; | |
49 private static final float SHADOW_HEIGHT_DP = 4.0f; | |
50 private static final float MINIMAL_BORDER_X_DP = 4.0f; | |
51 private static final float DARKEN_LAYOUTTAB_BRIGHTNESS = 0.3f; | |
52 private static final float MAX_LAYOUTTAB_DISPLACEMENT = 3.0f * TOOLBAR_HEIGH
T_DP; | |
53 | |
54 private static final float SNAP_BACK_THRESHOLD = 0.3f; | |
55 private static final long BASE_ANIMATION_DURATION_MS = 500; | |
56 | |
57 /** | |
58 * Panel's host interface. | |
59 */ | |
60 public interface ReaderModePanelHost { | |
61 /** | |
62 * @return Reader mode header background color. | |
63 */ | |
64 int getReaderModeHeaderBackgroundColor(); | |
65 | |
66 /** | |
67 * @return One of ReaderModeManager.POSSIBLE, NOT_POSSIBLE, STARTED cons
tants. | |
68 */ | |
69 int getReaderModeStatus(); | |
70 | |
71 /** | |
72 * @return An associated tab. | |
73 */ | |
74 Tab getTab(); | |
75 | |
76 /** | |
77 * @param X X-coordinate in dp | |
78 * @param Y Y-coordinate in dp | |
79 * @return Whether a given coordinates are within the bounds of the "dis
miss" button | |
80 */ | |
81 boolean isInsideDismissButton(float x, float y); | |
82 | |
83 /** | |
84 * Creates the Reader Mode control if necessary. | |
85 */ | |
86 void createReaderModeControl(); | |
87 | |
88 /** | |
89 * Destroys the Reader Mode control. | |
90 */ | |
91 void destroyReaderModeControl(); | |
92 } | |
93 | |
94 /** | |
95 * Layout integration interface. | |
96 */ | |
97 public interface ReaderModePanelLayoutDelegate { | |
98 /** | |
99 * Requests a next update to refresh the transforms and changing propert
ies. | |
100 */ | |
101 void requestUpdate(); | |
102 | |
103 /** | |
104 * Sets the brightness of the LayoutTab to a given value. | |
105 * @param v Brightness | |
106 */ | |
107 void setLayoutTabBrightness(float v); | |
108 | |
109 /** | |
110 * Sets the Y offset of the LayoutTab to a given value. | |
111 * @param v Y-offset in dp | |
112 */ | |
113 void setLayoutTabY(float v); | |
114 } | |
115 | |
116 /** | |
117 * Properties that can be animated by using a | |
118 * {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Ani
matable}. | |
119 */ | |
120 public enum Property { | |
121 /** | |
122 * Parametric vertical slider from | |
123 * -1.0 (panel is out of screen) to | |
124 * 0.0 (panel is on screen) to | |
125 * 1.0 (panel covers the entire screen) | |
126 */ | |
127 SLIDING_T, | |
128 /** | |
129 * Horizontal slider, offset in dp | |
130 */ | |
131 X, | |
132 } | |
133 | |
134 private float mSlidingT; | |
135 private float mX; | |
136 | |
137 private ScrollDirection mSwipeDirection; // set in swipeStarted | |
138 private float mInitialPanelDistanceFromBottom; // distance from the bottom
at swipeStarted | |
139 private float mInitialX; // X at swipeStarted | |
140 | |
141 /** | |
142 * The animation set. | |
143 */ | |
144 private ChromeAnimation<ChromeAnimation.Animatable<?>> mLayoutAnimations; | |
145 | |
146 private boolean mIsReaderModePanelHidden; | |
147 private boolean mIsReaderModePanelDismissed; | |
148 private boolean mIsFullscreenModeEntered; | |
149 private boolean mIsInfobarContainerShown; | |
150 | |
151 private ContentViewCore mDistilledContentViewCore; | |
152 private boolean mDidStartLoad; | |
153 private boolean mDidFinishLoad; | |
154 private WebContentsObserver mDistilledContentObserver; | |
155 private boolean mDidFirstNonEmptyDistilledPaint; | |
156 private ReaderModePanelLayoutDelegate mLayoutDelegate; | |
157 private WebContents mOriginalWebContent; | |
158 | |
159 private float mLayoutWidth; | |
160 private float mLayoutHeight; | |
161 private boolean mIsToolbarShowing; | |
162 private float mDpToPx; | |
163 | |
164 /** | |
165 * ContentViewClient state to override when the distilled ContentViewCore is
set on the Tab. | |
166 */ | |
167 private float mTopControlsOffsetYPix; | |
168 private float mContentOffsetYPix; | |
169 private float mOverdrawBottomHeightPix; | |
170 | |
171 /** | |
172 * The {@link ReaderModePanelHost} used to get reader mode status and the as
sociated tab. | |
173 */ | |
174 private final ReaderModePanelHost mReaderModeHost; | |
175 | |
176 /** | |
177 * The SceneLayer responsible for drawing the panel. | |
178 */ | |
179 private ReaderModeSceneLayer mSceneLayer; | |
180 | |
181 /** | |
182 * Non-animated button support. | |
183 */ | |
184 private boolean mAllowAnimatedButton; | |
185 private ReaderModeButtonView mReaderModeButtonView; | |
186 | |
187 public ReaderModePanel(ReaderModePanelHost readerModeHost, Context context)
{ | |
188 mReaderModeHost = readerModeHost; | |
189 | |
190 // Make sure all WebContents are destroyed when a tab is closed: crbug.c
om/496653 | |
191 mReaderModeHost.getTab().addObserver(new EmptyTabObserver() { | |
192 @Override | |
193 public void onDestroyed(Tab tab) { | |
194 destroyCachedOriginalWebContent(); | |
195 destroyDistilledContentViewCore(); | |
196 } | |
197 }); | |
198 | |
199 mAllowAnimatedButton = CommandLine.getInstance().hasSwitch( | |
200 ChromeSwitches.ENABLE_READER_MODE_BUTTON_ANIMATION); | |
201 | |
202 mLayoutWidth = 0.0f; | |
203 mLayoutHeight = 0.0f; | |
204 mDpToPx = 1.0f; | |
205 | |
206 mSlidingT = -1.0f; | |
207 mX = 0.0f; | |
208 | |
209 float dpToPx = context.getResources().getDisplayMetrics().density; | |
210 mSceneLayer = new ReaderModeSceneLayer(dpToPx); | |
211 } | |
212 | |
213 /** | |
214 * Get this panel's SceneLayer. | |
215 * NOTE(mdjones): This overrides a method in OverlayPanel once the refactor
is complete. | |
216 */ | |
217 public SceneLayer getSceneLayer() { | |
218 return mSceneLayer; | |
219 } | |
220 | |
221 /** | |
222 * Update this panel's SceneLayer. | |
223 * NOTE(mdjones): This overrides a method in OverlayPanel once the refactor
is complete. | |
224 * @param resourceManager Resource manager for static resources. | |
225 */ | |
226 public void updateSceneLayer(ResourceManager resourceManager) { | |
227 mSceneLayer.update(this, resourceManager); | |
228 } | |
229 | |
230 /** | |
231 * Destroys the panel and associated resources. | |
232 */ | |
233 public void onDestroy() { | |
234 hideButtonBar(); | |
235 } | |
236 | |
237 /** | |
238 * Set the layout delegate. | |
239 * @param layoutDelegate A {@link ReaderModePanelLayoutDelegate} to call. | |
240 */ | |
241 public void setLayoutDelegate(ReaderModePanelLayoutDelegate layoutDelegate)
{ | |
242 mLayoutDelegate = layoutDelegate; | |
243 requestUpdate(); | |
244 } | |
245 | |
246 // ChromeAnimation.Animatable<Property>: | |
247 | |
248 private void setSlidingT(float val) { | |
249 mSlidingT = val; | |
250 if (mLayoutDelegate != null) { | |
251 mLayoutDelegate.setLayoutTabBrightness(getTabBrightness()); | |
252 mLayoutDelegate.setLayoutTabY(getTabYOffset()); | |
253 } | |
254 } | |
255 | |
256 @Override | |
257 public void setProperty(Property prop, float val) { | |
258 switch (prop) { | |
259 case SLIDING_T: | |
260 setSlidingT(val); | |
261 break; | |
262 case X: | |
263 mX = val; | |
264 break; | |
265 } | |
266 } | |
267 | |
268 private static float clamp(float val, float lower, float higher) { | |
269 return val < lower ? lower : (val > higher ? higher : val); | |
270 } | |
271 | |
272 private static float interp(float factor, float start, float end) { | |
273 return start + clamp(factor, 0.0f, 1.0f) * (end - start); | |
274 } | |
275 | |
276 private float getPanelDistanceFromBottom() { | |
277 if (mSlidingT < 0.0f) return interp(mSlidingT + 1.0f, 0.0f, PANEL_HEIGHT
_DP); | |
278 return PANEL_HEIGHT_DP + interp(mSlidingT, 0.0f, getFullscreenHeight()); | |
279 } | |
280 | |
281 private float getSlidingTForPanelDistanceFromBottom(float distanceFromBottom
) { | |
282 if (distanceFromBottom >= PANEL_HEIGHT_DP) { | |
283 return interp( | |
284 (distanceFromBottom - PANEL_HEIGHT_DP) / getFullscreenHeight
(), | |
285 0.0f, 1.0f); | |
286 } | |
287 return interp( | |
288 (PANEL_HEIGHT_DP - distanceFromBottom) / PANEL_HEIGHT_DP, | |
289 0.0f, -1.0f); | |
290 } | |
291 | |
292 private float getDistilledContentDistanceFromBottom() { | |
293 if (mSlidingT < 0.0f) return interp(mSlidingT + 1.0f, -PANEL_HEIGHT_DP,
0.0f); | |
294 return interp(mSlidingT, 0.0f, getFullscreenHeight()); | |
295 } | |
296 | |
297 private static float snapBackSlidingT(float v) { | |
298 // We snap asymmetrically: 30% is enough to get it opened, but 70% is ne
cessary to dismiss. | |
299 v = (v < -1.0f + SNAP_BACK_THRESHOLD) ? v : (v >= SNAP_BACK_THRESHOLD ?
v : 0.0f); | |
300 return Math.signum(v); | |
301 } | |
302 | |
303 private static float snapBackX(float v) { | |
304 // Horizontally we snap symmetrically: more than 70% to each side to dis
miss. | |
305 v = (v < -1.0f + SNAP_BACK_THRESHOLD) ? v : (v >= 1.0f - SNAP_BACK_THRES
HOLD ? v : 0.0f); | |
306 return Math.signum(v); | |
307 } | |
308 | |
309 // Gesture handling: | |
310 | |
311 /** | |
312 * @param direction Swipe direction to test | |
313 * @return Whether the swipe in a given direction is enabled | |
314 */ | |
315 public boolean isSwipeEnabled(ScrollDirection direction) { | |
316 return !isAnimating(); | |
317 } | |
318 | |
319 /** | |
320 * Called when the swipe is started. | |
321 * @param direction Swipe direction | |
322 * @param x X-coordinate of the starting point in dp | |
323 * @param y Y-coordinate of the starting point in dp | |
324 */ | |
325 public void swipeStarted(ScrollDirection direction, float x, float y) { | |
326 if (isAnimating()) return; | |
327 | |
328 mSwipeDirection = direction; | |
329 mInitialPanelDistanceFromBottom = getPanelDistanceFromBottom(); | |
330 mX = getX(); | |
331 | |
332 if (mSwipeDirection == ScrollDirection.UP) activatePreviewOfDistilledMod
e(); | |
333 | |
334 requestUpdate(); | |
335 } | |
336 | |
337 /** | |
338 * Called when the swipe is continued. | |
339 * @param tx X-offset since the start of the swipe in dp | |
340 * @param ty Y-offset since the start of the swipe in dp | |
341 */ | |
342 public void swipeUpdated(float x, float y, float dx, float dy, float tx, flo
at ty) { | |
343 if (isAnimating()) return; | |
344 | |
345 if (mSwipeDirection == ScrollDirection.LEFT || mSwipeDirection == Scroll
Direction.RIGHT) { | |
346 setProperty(ReaderModePanel.Property.X, clamp(mInitialX + tx, | |
347 -mLayoutWidth + MINIMAL_BORDER_X_DP, mLayoutWidth - MINIMAL_
BORDER_X_DP)); | |
348 } else { | |
349 setProperty(ReaderModePanel.Property.SLIDING_T, | |
350 getSlidingTForPanelDistanceFromBottom(mInitialPanelDistanceF
romBottom - ty)); | |
351 } | |
352 requestUpdate(); | |
353 } | |
354 | |
355 /** | |
356 * Called when the swipe is finished. | |
357 */ | |
358 public void swipeFinished() { | |
359 if (isAnimating()) return; | |
360 | |
361 final float snappedX = snapBackX(mX / mLayoutWidth) * mLayoutWidth; | |
362 final float snappedSlidingT = snapBackSlidingT(mSlidingT); | |
363 if (snappedX <= -mLayoutWidth || snappedX >= mLayoutWidth) dismissButton
Bar(); | |
364 if (snappedSlidingT < 0.0f) dismissButtonBar(); | |
365 | |
366 animateTo(snappedX, snappedSlidingT, true); | |
367 } | |
368 | |
369 // Panel layout handling: | |
370 | |
371 /** | |
372 * @return Whether the panel should be shown. | |
373 */ | |
374 public boolean isShowing() { | |
375 return isPanelWithinScreenBounds() || isAnimating() || mDistilledContent
ViewCore != null; | |
376 } | |
377 | |
378 /** | |
379 * @return Whether the panel is within screen bounds. | |
380 */ | |
381 private boolean isPanelWithinScreenBounds() { | |
382 return mSlidingT > -1.0f; | |
383 } | |
384 | |
385 /** | |
386 * @return The fullscreen height. | |
387 */ | |
388 private float getFullscreenHeight() { | |
389 return mLayoutHeight + TOOLBAR_HEIGHT_DP; | |
390 } | |
391 | |
392 public float getFullscreenY(float y) { | |
393 if (mIsToolbarShowing) y += TOOLBAR_HEIGHT_DP * mDpToPx; | |
394 return y; | |
395 } | |
396 | |
397 public float getPanelY() { | |
398 return getFullscreenHeight() - getPanelDistanceFromBottom() - SHADOW_HEI
GHT_DP; | |
399 } | |
400 | |
401 public float getDistilledContentY() { | |
402 return getFullscreenHeight() - getDistilledContentDistanceFromBottom() -
SHADOW_HEIGHT_DP; | |
403 } | |
404 | |
405 public float getWidth() { | |
406 return mLayoutWidth; | |
407 } | |
408 | |
409 public float getPanelHeight() { | |
410 return getPanelDistanceFromBottom(); | |
411 } | |
412 | |
413 public float getMarginTop() { | |
414 return SHADOW_HEIGHT_DP; | |
415 } | |
416 | |
417 public float getDistilledHeight() { | |
418 return getDistilledContentDistanceFromBottom(); | |
419 } | |
420 | |
421 public float getX() { | |
422 return mX; | |
423 } | |
424 | |
425 public float getTextOpacity() { | |
426 return interp(mSlidingT, 1.0f, 0.0f); | |
427 } | |
428 | |
429 public float getTabBrightness() { | |
430 return interp(mSlidingT, 1.0f, DARKEN_LAYOUTTAB_BRIGHTNESS); | |
431 } | |
432 | |
433 public float getTabYOffset() { | |
434 return interp(mSlidingT, 0.0f, -MAX_LAYOUTTAB_DISPLACEMENT); | |
435 } | |
436 | |
437 /** | |
438 * @param currentOffset The current top controls offset in dp. | |
439 * @return {@link Float#NaN} if no offset should be used, or a value in dp | |
440 * if the top controls offset should be overridden. | |
441 */ | |
442 public float getTopControlsOffset(float currentOffsetDp) { | |
443 if (mSlidingT <= 0.0f) return Float.NaN; | |
444 return MathUtils.clamp(getTabYOffset(), -TOOLBAR_HEIGHT_DP, Math.min(cur
rentOffsetDp, 0f)); | |
445 } | |
446 | |
447 | |
448 public ContentViewCore getDistilledContentViewCore() { | |
449 return mDistilledContentViewCore; | |
450 } | |
451 | |
452 public boolean didFirstNonEmptyDistilledPaint() { | |
453 return mDidFirstNonEmptyDistilledPaint; | |
454 } | |
455 | |
456 public int getReaderModeHeaderBackgroundColor() { | |
457 return mReaderModeHost.getReaderModeHeaderBackgroundColor(); | |
458 } | |
459 | |
460 /** | |
461 * Called when the size of the view has changed. | |
462 * | |
463 * @param width The new width in dp. | |
464 * @param height The new width in dp. | |
465 * @param isToolbarShowing Whether the Toolbar is showing. | |
466 * @param dpToPx Multipler to convert from dp to pixels. | |
467 */ | |
468 public void onSizeChanged(float width, float height, boolean isToolbarShowin
g, float dpToPx) { | |
469 mLayoutWidth = width; | |
470 mLayoutHeight = height; | |
471 mIsToolbarShowing = isToolbarShowing; | |
472 mDpToPx = dpToPx; | |
473 } | |
474 | |
475 // Layout integration: | |
476 | |
477 /** | |
478 * Requests a new frame to be updated and rendered. | |
479 */ | |
480 private void requestUpdate() { | |
481 if (mLayoutDelegate != null) mLayoutDelegate.requestUpdate(); | |
482 } | |
483 | |
484 // Animation handling: | |
485 | |
486 /** | |
487 * @return Whether a panel animation is in progress. | |
488 */ | |
489 private boolean isAnimating() { | |
490 return mLayoutAnimations != null && !mLayoutAnimations.finished(); | |
491 } | |
492 | |
493 /** | |
494 * Animates to a given target value. | |
495 * @param targetX A target value for the X parameter | |
496 * @param targetSlidingT A target value for the SlidingT parameter | |
497 */ | |
498 private void animateTo(float targetX, float targetSlidingT, boolean animate)
{ | |
499 if (targetSlidingT > 0.0f) activatePreviewOfDistilledMode(); | |
500 | |
501 if (isAnimating()) { | |
502 mLayoutAnimations.cancel(this, Property.SLIDING_T); | |
503 mLayoutAnimations.cancel(this, Property.X); | |
504 } | |
505 if (mLayoutAnimations == null || mLayoutAnimations.finished()) { | |
506 mLayoutAnimations = new ChromeAnimation<Animatable<?>>(); | |
507 } | |
508 | |
509 mLayoutAnimations.add(createAnimation( | |
510 this, Property.SLIDING_T, mSlidingT, targetSlidingT, | |
511 BASE_ANIMATION_DURATION_MS, 0, false, | |
512 ChromeAnimation.getDecelerateInterpolator())); | |
513 mLayoutAnimations.add(createAnimation( | |
514 this, Property.X, mX, targetX, | |
515 BASE_ANIMATION_DURATION_MS, 0, false, | |
516 ChromeAnimation.getDecelerateInterpolator())); | |
517 mLayoutAnimations.start(); | |
518 | |
519 if (!animate) mLayoutAnimations.updateAndFinish(); | |
520 requestUpdate(); | |
521 } | |
522 | |
523 /** | |
524 * Steps the animation forward and updates all the animated values. | |
525 * @param time The current time of the app in ms. | |
526 * @param jumpToEnd Whether to finish the animation. | |
527 * @return Whether the animation was finished. | |
528 */ | |
529 public boolean onUpdateAnimation(long time, boolean jumpToEnd) { | |
530 boolean finished = true; | |
531 if (mLayoutAnimations != null) { | |
532 if (jumpToEnd) { | |
533 finished = mLayoutAnimations.finished(); | |
534 mLayoutAnimations.updateAndFinish(); | |
535 } else { | |
536 finished = mLayoutAnimations.update(time); | |
537 } | |
538 | |
539 if (finished || jumpToEnd) { | |
540 mLayoutAnimations = null; | |
541 onAnimationFinished(); | |
542 } | |
543 requestUpdate(); | |
544 } | |
545 return finished; | |
546 } | |
547 | |
548 /** | |
549 * Called when layout-specific actions are needed after the animation finish
es. | |
550 */ | |
551 private void onAnimationFinished() { | |
552 if (mSlidingT >= 1.0f) enterDistilledMode(); | |
553 updateBottomButtonBar(); | |
554 } | |
555 | |
556 // Gesture handling: | |
557 | |
558 /** | |
559 * @param y The y coordinate in dp. | |
560 * @return Whether the given |y| coordinate is inside the Reader mode area. | |
561 */ | |
562 public boolean isYCoordinateInsideReaderModePanel(float y) { | |
563 return y >= getPanelY() || y >= getDistilledContentY(); | |
564 } | |
565 | |
566 /** | |
567 * Handles a click in the panel area. | |
568 * @param x X-coordinate in dp | |
569 * @param y Y-coordinate in dp | |
570 */ | |
571 public void handleClick(long time, float x, float y) { | |
572 if (mReaderModeHost.isInsideDismissButton(x * mDpToPx + mX, PANEL_HEIGHT
_DP / 2)) { | |
573 dismissButtonBar(); | |
574 return; | |
575 } | |
576 | |
577 animateTo(mX, 1.0f, true); | |
578 } | |
579 | |
580 /** | |
581 * @return Whether the reader mode could be currently allowed. | |
582 */ | |
583 public boolean isReaderModeCurrentlyAllowed() { | |
584 return !mIsReaderModePanelHidden && !mIsReaderModePanelDismissed | |
585 && !mIsFullscreenModeEntered && !mIsInfobarContainerShown | |
586 && mReaderModeHost.getTab() != null | |
587 && mReaderModeHost.getTab().getContentViewCore() != null | |
588 && mReaderModeHost.getTab().getContentViewCore().getContext() !=
null | |
589 && mReaderModeHost.getTab().getWebContents() != null; | |
590 } | |
591 | |
592 private void nonAnimatedUpdateButtomButtonBar() { | |
593 final int status = mReaderModeHost.getReaderModeStatus(); | |
594 final Tab tab = mReaderModeHost.getTab(); | |
595 | |
596 if (mReaderModeButtonView != null | |
597 && (status != ReaderModeManager.POSSIBLE || !isReaderModeCurrent
lyAllowed())) { | |
598 // Unfortunately, dismiss() couldn't be used because it might attemp
t to remove a view | |
599 // while in onLayout, thus causing crash. | |
600 final ReaderModeButtonView buttonView = mReaderModeButtonView; | |
601 mReaderModeButtonView.post(new Runnable() { | |
602 @Override | |
603 public void run() { | |
604 // Unfortunately, dismiss() couldn't be used because it migh
t attempt | |
605 // to remove a view while in onLayout, thus causing crash. | |
606 buttonView.removeFromParentView(); | |
607 } | |
608 }); | |
609 mReaderModeButtonView = null; | |
610 return; | |
611 } | |
612 | |
613 if (mReaderModeButtonView == null | |
614 && (status == ReaderModeManager.POSSIBLE && isReaderModeCurrentl
yAllowed())) { | |
615 mReaderModeButtonView = ReaderModeButtonView.create(tab.getContentVi
ewCore(), | |
616 new ReaderModeButtonViewDelegate() { | |
617 @Override | |
618 public void onSwipeAway() { | |
619 dismissButtonBar(); | |
620 } | |
621 | |
622 @Override | |
623 public void onClick() { | |
624 nonAnimatedEnterDistilledMode(); | |
625 } | |
626 }); | |
627 } | |
628 } | |
629 | |
630 /** | |
631 * Updates the visibility of the reader mode button bar as required. | |
632 */ | |
633 public void updateBottomButtonBar() { | |
634 if (!mAllowAnimatedButton) { | |
635 nonAnimatedUpdateButtomButtonBar(); | |
636 return; | |
637 } | |
638 | |
639 if (isAnimating()) { | |
640 mReaderModeHost.createReaderModeControl(); | |
641 return; | |
642 } | |
643 | |
644 final int status = mReaderModeHost.getReaderModeStatus(); | |
645 if (isPanelWithinScreenBounds() | |
646 && (status != ReaderModeManager.POSSIBLE || !isReaderModeCurrent
lyAllowed())) { | |
647 animateTo(0.0f, -1.0f, true); | |
648 mReaderModeHost.destroyReaderModeControl(); | |
649 destroyCachedOriginalWebContent(); | |
650 destroyDistilledContentViewCore(); | |
651 requestUpdate(); | |
652 return; | |
653 } | |
654 | |
655 if (!isPanelWithinScreenBounds() | |
656 && (status == ReaderModeManager.POSSIBLE && isReaderModeCurrentl
yAllowed())) { | |
657 animateTo(0.0f, 0.0f, true); | |
658 mReaderModeHost.createReaderModeControl(); | |
659 requestUpdate(); | |
660 return; | |
661 } | |
662 } | |
663 | |
664 private ContentViewCore createDistillerContentViewCore( | |
665 Context context, WindowAndroid windowAndroid) { | |
666 boolean isHostTabIncognito = | |
667 mReaderModeHost.getTab().getContentViewCore().getWebContents().i
sIncognito(); | |
668 ContentViewCore cvc = new ContentViewCore(context); | |
669 ContentView cv = ContentView.createContentView(context, cvc); | |
670 cvc.initialize(cv, cv, WebContentsFactory.createWebContents(isHostTabInc
ognito, true), | |
671 windowAndroid); | |
672 cvc.setContentViewClient(new ContentViewClient() { | |
673 @Override | |
674 public void onOffsetsForFullscreenChanged(float topControlsOffsetYPi
x, | |
675 float contentOffsetYPix, float overdrawBottomHeightPix) { | |
676 super.onOffsetsForFullscreenChanged(topControlsOffsetYPix, conte
ntOffsetYPix, | |
677 overdrawBottomHeightPix); | |
678 mTopControlsOffsetYPix = topControlsOffsetYPix; | |
679 mContentOffsetYPix = contentOffsetYPix; | |
680 mOverdrawBottomHeightPix = overdrawBottomHeightPix; | |
681 } | |
682 }); | |
683 return cvc; | |
684 } | |
685 | |
686 /** | |
687 * Prepares the distilled mode. | |
688 */ | |
689 public void activatePreviewOfDistilledMode() { | |
690 final long start = SystemClock.elapsedRealtime(); | |
691 | |
692 if (mDistilledContentViewCore != null) return; | |
693 | |
694 mDidFirstNonEmptyDistilledPaint = false; | |
695 mDidStartLoad = false; | |
696 mDidFinishLoad = false; | |
697 | |
698 destroyCachedOriginalWebContent(); | |
699 mDistilledContentViewCore = createDistillerContentViewCore( | |
700 mReaderModeHost.getTab().getContentViewCore().getContext(), | |
701 mReaderModeHost.getTab().getWindowAndroid()); | |
702 | |
703 mergeNavigationHistory(mDistilledContentViewCore.getWebContents(), | |
704 mReaderModeHost.getTab().getWebContents()); | |
705 | |
706 mDistilledContentObserver = new WebContentsObserver( | |
707 mDistilledContentViewCore.getWebContents()) { | |
708 @Override | |
709 public void didFirstVisuallyNonEmptyPaint() { | |
710 super.didFirstVisuallyNonEmptyPaint(); | |
711 mDidFirstNonEmptyDistilledPaint = true; | |
712 | |
713 RecordHistogram.recordTimesHistogram("DomDistiller.Time.SwipeToP
aint", | |
714 SystemClock.elapsedRealtime() - start, TimeUnit.MILLISEC
ONDS); | |
715 } | |
716 | |
717 @Override | |
718 public void didStartLoading(String url) { | |
719 super.didStartLoading(url); | |
720 mDidStartLoad = true; | |
721 } | |
722 | |
723 @Override | |
724 public void didFinishLoad(long frameId, String validatedUrl, boolean
isMainFrame) { | |
725 super.didFinishLoad(frameId, validatedUrl, isMainFrame); | |
726 if (isMainFrame) mDidFinishLoad = true; | |
727 } | |
728 }; | |
729 mReaderModeHost.getTab().attachOverlayContentViewCore( | |
730 mDistilledContentViewCore, true, false); | |
731 DomDistillerTabUtils.distillAndView( | |
732 mReaderModeHost.getTab().getContentViewCore().getWebContents(), | |
733 mDistilledContentViewCore.getWebContents()); | |
734 mDistilledContentViewCore.onShow(); | |
735 } | |
736 | |
737 private void nonAnimatedEnterDistilledMode() { | |
738 RecordUserAction.record("DomDistiller_DistilledPageOpened"); | |
739 DomDistillerTabUtils.distillCurrentPageAndView(mReaderModeHost.getTab().
getWebContents()); | |
740 nonAnimatedUpdateButtomButtonBar(); | |
741 } | |
742 | |
743 private static void mergeNavigationHistory(WebContents target, WebContents s
ource) { | |
744 target.getNavigationController().clearHistory(); | |
745 NavigationController distilled = target.getNavigationController(); | |
746 NavigationController original = source.getNavigationController(); | |
747 if (distilled.canPruneAllButLastCommitted()) { | |
748 distilled.copyStateFromAndPrune(original, false); | |
749 } else if (distilled.canCopyStateOver()) { | |
750 distilled.copyStateFrom(original); | |
751 } | |
752 } | |
753 | |
754 private void enterDistilledMode() { | |
755 if (!isReaderModeCurrentlyAllowed()) return; | |
756 | |
757 RecordUserAction.record("DomDistiller_DistilledPageOpened"); | |
758 mSlidingT = -1.0f; | |
759 requestUpdate(); | |
760 | |
761 mDistilledContentViewCore.getWebContents().updateTopControlsState(true,
false, false); | |
762 | |
763 mReaderModeHost.getTab().detachOverlayContentViewCore(mDistilledContentV
iewCore); | |
764 mDistilledContentObserver.destroy(); | |
765 mDistilledContentObserver = null; | |
766 | |
767 mOriginalWebContent = mReaderModeHost.getTab().getWebContents(); | |
768 | |
769 mDistilledContentViewCore.setContentViewClient(new ContentViewClient()); | |
770 mReaderModeHost.getTab().swapContentViewCore(mDistilledContentViewCore,
false, | |
771 mDidStartLoad, mDidFinishLoad); | |
772 mDistilledContentViewCore.getContentViewClient().onOffsetsForFullscreenC
hanged( | |
773 mTopControlsOffsetYPix, mContentOffsetYPix, mOverdrawBottomHeigh
tPix); | |
774 | |
775 mDistilledContentViewCore = null; | |
776 destroyDistilledContentViewCore(); | |
777 | |
778 if (mLayoutDelegate != null) { | |
779 mLayoutDelegate.setLayoutTabBrightness(1.0f); | |
780 mLayoutDelegate.setLayoutTabY(0.0f); | |
781 } | |
782 | |
783 updateBottomButtonBar(); | |
784 } | |
785 | |
786 private void destroyCachedOriginalWebContent() { | |
787 if (mOriginalWebContent != null) { | |
788 mOriginalWebContent.destroy(); | |
789 mOriginalWebContent = null; | |
790 } | |
791 } | |
792 | |
793 private void destroyDistilledContentViewCore() { | |
794 if (mDistilledContentObserver != null) { | |
795 mDistilledContentObserver.destroy(); | |
796 mDistilledContentObserver = null; | |
797 } | |
798 | |
799 if (mDistilledContentViewCore == null) return; | |
800 | |
801 mReaderModeHost.getTab().detachOverlayContentViewCore(mDistilledContentV
iewCore); | |
802 | |
803 mDistilledContentViewCore.getWebContents().destroy(); | |
804 mDistilledContentViewCore.destroy(); | |
805 mDistilledContentViewCore = null; | |
806 } | |
807 | |
808 /** | |
809 * Hides the reader mode button bar if shown. | |
810 */ | |
811 public void hideButtonBar() { | |
812 mIsReaderModePanelHidden = true; | |
813 mLayoutAnimations = null; | |
814 updateBottomButtonBar(); | |
815 } | |
816 | |
817 /** | |
818 * Dismisses the reader mode button bar if shown. | |
819 */ | |
820 public void dismissButtonBar() { | |
821 mIsReaderModePanelDismissed = true; | |
822 mLayoutAnimations = null; | |
823 updateBottomButtonBar(); | |
824 } | |
825 | |
826 /** | |
827 * Shows the reader mode button bar if necessary. | |
828 */ | |
829 public void unhideButtonBar() { | |
830 mIsReaderModePanelHidden = false; | |
831 updateBottomButtonBar(); | |
832 } | |
833 | |
834 /** | |
835 * Temporarily hides the reader mode button while the video is shown. | |
836 */ | |
837 public void onEnterFullscreen() { | |
838 mIsFullscreenModeEntered = true; | |
839 mLayoutAnimations = null; | |
840 updateBottomButtonBar(); | |
841 } | |
842 | |
843 /** | |
844 * Re-shows the reader mode button if necessary once the video is exited. | |
845 */ | |
846 public void onExitFullscreen() { | |
847 mIsFullscreenModeEntered = false; | |
848 updateBottomButtonBar(); | |
849 } | |
850 | |
851 /** | |
852 * Temporarily hides the reader mode button while the infobars are shown. | |
853 */ | |
854 public void onShowInfobarContainer() { | |
855 mIsInfobarContainerShown = true; | |
856 mLayoutAnimations = null; | |
857 updateBottomButtonBar(); | |
858 } | |
859 | |
860 /** | |
861 * Re-shows the reader mode button if necessary once the infobars are dismis
sed. | |
862 */ | |
863 public void onHideInfobarContainer() { | |
864 mIsInfobarContainerShown = false; | |
865 updateBottomButtonBar(); | |
866 } | |
867 | |
868 /** | |
869 * @param tab A {@link Tab}. | |
870 * @return The panel associated with a given Tab. | |
871 */ | |
872 public static ReaderModePanel getReaderModePanel(Tab tab) { | |
873 ReaderModeManager manager = tab.getReaderModeManager(); | |
874 if (manager == null) return null; | |
875 return manager.getReaderModePanel(); | |
876 } | |
877 } | |
OLD | NEW |