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 android.app.Activity; |
| 8 import android.content.Context; |
| 9 import android.text.TextUtils; |
| 10 |
| 11 import com.google.android.apps.chrome.R; |
| 12 |
| 13 import org.chromium.base.CommandLine; |
| 14 import org.chromium.base.ObserverList; |
| 15 import org.chromium.base.metrics.RecordHistogram; |
| 16 import org.chromium.chrome.browser.ApplicationSwitches; |
| 17 import org.chromium.chrome.browser.ChromeSwitches; |
| 18 import org.chromium.chrome.browser.ChromeVersionInfo; |
| 19 import org.chromium.chrome.browser.CompositorChromeActivity; |
| 20 import org.chromium.chrome.browser.EmptyTabObserver; |
| 21 import org.chromium.chrome.browser.Tab; |
| 22 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager; |
| 23 import org.chromium.chrome.browser.contextualsearch.ContextualSearchObserver; |
| 24 import org.chromium.chrome.browser.dom_distiller.ReaderModePanel.ReaderModePanel
Host; |
| 25 import org.chromium.chrome.browser.gsa.GSAContextDisplaySelection; |
| 26 import org.chromium.chrome.browser.tab.ChromeTab; |
| 27 import org.chromium.components.dom_distiller.content.DistillablePageUtils; |
| 28 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; |
| 29 import org.chromium.content_public.browser.WebContents; |
| 30 import org.chromium.content_public.browser.WebContentsObserver; |
| 31 import org.chromium.ui.base.DeviceFormFactor; |
| 32 |
| 33 /** |
| 34 * Manages UI effects for reader mode including hiding and showing the |
| 35 * reader mode and reader mode preferences toolbar icon and hiding the |
| 36 * top controls when a reader mode page has finished loading. |
| 37 */ |
| 38 public class ReaderModeManager extends EmptyTabObserver |
| 39 implements ContextualSearchObserver, ReaderModePanelHost { |
| 40 |
| 41 /** |
| 42 * Observer for changes to the current status of reader mode. |
| 43 */ |
| 44 public static interface ReaderModeManagerObserver { |
| 45 /** |
| 46 * Triggered on changes to the reader mode status for the owning tab. |
| 47 * |
| 48 * @param readerModeStatus The current status of reader mode. |
| 49 * @see ReaderModeManager#POSSIBLE |
| 50 * @see ReaderModeManager#NOT_POSSIBLE |
| 51 * @see ReaderModeManager#STARTED |
| 52 */ |
| 53 void onReaderModeStatusChanged(int readerModeStatus); |
| 54 } |
| 55 |
| 56 /** |
| 57 * POSSIBLE means reader mode can be entered. |
| 58 */ |
| 59 public static final int POSSIBLE = 0; |
| 60 /** |
| 61 * NOT_POSSIBLE means reader mode cannot be entered. |
| 62 */ |
| 63 public static final int NOT_POSSIBLE = 1; |
| 64 /** |
| 65 * STARTED means reader mode is currently in reader mode. |
| 66 */ |
| 67 public static final int STARTED = 2; |
| 68 |
| 69 /** |
| 70 * JavaScript that can be executed to tell whether or not a page can be view
ed in reader mode. |
| 71 */ |
| 72 private static final String sIsReadableJs = DomDistillerUrlUtils.getIsDistil
lableJs(); |
| 73 |
| 74 /** |
| 75 * The url of the last page visited if the last page was reader mode page.
Otherwise null. |
| 76 */ |
| 77 private String mReaderModePageUrl; |
| 78 |
| 79 /** |
| 80 * Whether the page is an article or not. |
| 81 */ |
| 82 private int mReaderModeStatus = NOT_POSSIBLE; |
| 83 |
| 84 /** |
| 85 * Whether the fact that the current web page was distillable or not has bee
n recorded. |
| 86 */ |
| 87 private boolean mIsUmaRecorded; |
| 88 |
| 89 private WebContentsObserver mWebContentsObserver; |
| 90 |
| 91 private final Tab mTab; |
| 92 |
| 93 private final ReaderModePanel mReaderModePanel; |
| 94 |
| 95 private final ObserverList<ReaderModeManagerObserver> mObservers; |
| 96 |
| 97 private final int mHeaderBackgroundColor; |
| 98 |
| 99 public ReaderModeManager(Tab tab, Context context) { |
| 100 mTab = tab; |
| 101 mTab.addObserver(this); |
| 102 mObservers = new ObserverList<ReaderModeManagerObserver>(); |
| 103 mReaderModePanel = isEnabled(context) ? new ReaderModePanel(this) : null
; |
| 104 mHeaderBackgroundColor = context != null |
| 105 ? context.getResources().getColor(R.color.reader_mode_header_bg)
: 0; |
| 106 } |
| 107 |
| 108 /** |
| 109 * Adds an observer to be notified about changes to the reader mode status. |
| 110 */ |
| 111 public void addObserver(ReaderModeManagerObserver observer) { |
| 112 mObservers.addObserver(observer); |
| 113 } |
| 114 |
| 115 /** |
| 116 * Removes an observer from receiving updates about the reader mode status c
hanges. |
| 117 */ |
| 118 public void removeObserver(ReaderModeManagerObserver observer) { |
| 119 mObservers.removeObserver(observer); |
| 120 } |
| 121 |
| 122 // EmptyTabObserver: |
| 123 @Override |
| 124 public void onDestroyed(Tab tab) { |
| 125 ContextualSearchManager contextualSearchManager = getContextualSearchMan
ager(tab); |
| 126 if (contextualSearchManager != null) contextualSearchManager.removeObser
ver(this); |
| 127 |
| 128 if (mReaderModePanel != null) mReaderModePanel.onDestroy(); |
| 129 |
| 130 if (mWebContentsObserver != null) { |
| 131 mWebContentsObserver.destroy(); |
| 132 mWebContentsObserver = null; |
| 133 } |
| 134 } |
| 135 |
| 136 @Override |
| 137 public void onContentChanged(Tab tab) { |
| 138 if (mWebContentsObserver != null) { |
| 139 mWebContentsObserver.destroy(); |
| 140 mWebContentsObserver = null; |
| 141 } |
| 142 if (tab.getWebContents() != null) { |
| 143 mWebContentsObserver = createWebContentsObserver(tab.getWebContents(
)); |
| 144 if (DomDistillerUrlUtils.isDistilledPage(tab.getUrl())) { |
| 145 mReaderModeStatus = STARTED; |
| 146 mReaderModePageUrl = tab.getUrl(); |
| 147 sendReaderModeStatusChangedNotification(); |
| 148 } |
| 149 } |
| 150 ContextualSearchManager contextualSearchManager = getContextualSearchMan
ager(tab); |
| 151 if (contextualSearchManager != null) contextualSearchManager.addObserver
(this); |
| 152 } |
| 153 |
| 154 // ContextualSearchObserver: |
| 155 @Override |
| 156 public void onShowContextualSearch(GSAContextDisplaySelection selectionConte
xt) { |
| 157 if (mReaderModePanel != null) mReaderModePanel.hideButtonBar(); |
| 158 } |
| 159 |
| 160 @Override |
| 161 public void onHideContextualSearch() { |
| 162 if (mReaderModePanel != null) mReaderModePanel.unhideButtonBar(); |
| 163 } |
| 164 |
| 165 // ReaderModePanelHost: |
| 166 |
| 167 // TODO(aruslan): use the one in ChromeSwitches once it's rolled. |
| 168 private static final String ENABLE_READER_MODE_BUTTON_ANIMATION = |
| 169 "enable-dom-distiller-button-animation"; |
| 170 |
| 171 @Override |
| 172 public boolean allowReaderModeButtonAnimation() { |
| 173 return CommandLine.getInstance().hasSwitch( |
| 174 ENABLE_READER_MODE_BUTTON_ANIMATION); |
| 175 } |
| 176 |
| 177 @Override |
| 178 public int getReaderModeHeaderBackgroundColor() { |
| 179 return mHeaderBackgroundColor; |
| 180 } |
| 181 |
| 182 @Override |
| 183 public int getReaderModeStatus() { |
| 184 return mReaderModeStatus; |
| 185 } |
| 186 |
| 187 @Override |
| 188 public Tab getTab() { |
| 189 return mTab; |
| 190 } |
| 191 |
| 192 @Override |
| 193 public boolean isInsideDismissButton(float x, float y) { |
| 194 if (!(mTab instanceof ChromeTab)) return false; |
| 195 ReaderModeActivityDelegate delegate = ((ChromeTab) mTab).getReaderModeAc
tivityDelegate(); |
| 196 if (delegate == null) return false; |
| 197 return delegate.getReaderModeControl().isInsideDismissButton(x, y); |
| 198 } |
| 199 |
| 200 /** |
| 201 * @return The panel associated with the managed tab. |
| 202 */ |
| 203 public ReaderModePanel getReaderModePanel() { |
| 204 return mReaderModePanel; |
| 205 } |
| 206 |
| 207 private WebContentsObserver createWebContentsObserver(WebContents webContent
s) { |
| 208 return new WebContentsObserver(webContents) { |
| 209 @Override |
| 210 public void didFinishLoad(long frameId, String validatedUrl, boolean
isMainFrame) { |
| 211 if (!isMainFrame) return; |
| 212 if (DomDistillerUrlUtils.isDistilledPage(mTab.getUrl())) return; |
| 213 updateStatusBasedOnReaderModeCriteria(true); |
| 214 } |
| 215 |
| 216 @Override |
| 217 public void didStartProvisionalLoadForFrame(long frameId, long paren
tFrameId, |
| 218 boolean isMainFrame, String validatedUrl, boolean isErrorPag
e, |
| 219 boolean isIframeSrcdoc) { |
| 220 if (!isMainFrame) return; |
| 221 if (DomDistillerUrlUtils.isDistilledPage(validatedUrl)) { |
| 222 mReaderModeStatus = STARTED; |
| 223 sendReaderModeStatusChangedNotification(); |
| 224 mReaderModePageUrl = validatedUrl; |
| 225 } |
| 226 } |
| 227 |
| 228 @Override |
| 229 public void didNavigateMainFrame(String url, String baseUrl, |
| 230 boolean isNavigationToDifferentPage, boolean isNavigationInP
age, |
| 231 int statusCode) { |
| 232 // TODO(cjhopman): This should possibly ignore navigations that
replace the entry |
| 233 // (like those from history.replaceState()). |
| 234 if (isNavigationInPage) return; |
| 235 if (DomDistillerUrlUtils.isDistilledPage(url)) return; |
| 236 |
| 237 mReaderModeStatus = POSSIBLE; |
| 238 if (!TextUtils.equals(url, |
| 239 DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl( |
| 240 mReaderModePageUrl))) { |
| 241 mReaderModeStatus = NOT_POSSIBLE; |
| 242 mIsUmaRecorded = false; |
| 243 updateStatusBasedOnReaderModeCriteria(false); |
| 244 } |
| 245 mReaderModePageUrl = null; |
| 246 sendReaderModeStatusChangedNotification(); |
| 247 } |
| 248 }; |
| 249 } |
| 250 |
| 251 // Updates reader mode status based on whether or not the page should be vie
wed in reader mode. |
| 252 private void updateStatusBasedOnReaderModeCriteria(final boolean forceRecord
) { |
| 253 if (mTab.getWebContents() == null) return; |
| 254 if (mTab.getContentViewCore() == null) return; |
| 255 |
| 256 DistillablePageUtils.isPageDistillable(mTab.getWebContents(), |
| 257 mTab.getContentViewCore().getIsMobileOptimizedHint(), |
| 258 new DistillablePageUtils.PageDistillableCallback() { |
| 259 @Override |
| 260 public void onIsPageDistillableResult(boolean isDistillable)
{ |
| 261 if (isDistillable) { |
| 262 mReaderModeStatus = POSSIBLE; |
| 263 } else { |
| 264 mReaderModeStatus = NOT_POSSIBLE; |
| 265 } |
| 266 if (!mIsUmaRecorded && (mReaderModeStatus == POSSIBLE ||
forceRecord)) { |
| 267 mIsUmaRecorded = true; |
| 268 RecordHistogram.recordBooleanHistogram( |
| 269 "DomDistiller.PageDistillable", mReaderModeS
tatus == POSSIBLE); |
| 270 } |
| 271 sendReaderModeStatusChangedNotification(); |
| 272 } |
| 273 }); |
| 274 } |
| 275 |
| 276 private void sendReaderModeStatusChangedNotification() { |
| 277 for (ReaderModeManagerObserver observer : mObservers) { |
| 278 observer.onReaderModeStatusChanged(mReaderModeStatus); |
| 279 } |
| 280 if (mReaderModePanel != null) mReaderModePanel.updateBottomButtonBar(); |
| 281 } |
| 282 |
| 283 private ContextualSearchManager getContextualSearchManager(Tab tab) { |
| 284 if (tab == null || tab.getWindowAndroid() == null) return null; |
| 285 Activity activity = tab.getWindowAndroid().getActivity().get(); |
| 286 if (!(activity instanceof CompositorChromeActivity)) return null; |
| 287 return ((CompositorChromeActivity) activity).getContextualSearchManager(
); |
| 288 } |
| 289 |
| 290 /** |
| 291 * @return Whether Reader mode and its new UI are enabled. |
| 292 * @param context A context |
| 293 */ |
| 294 public static boolean isEnabled(Context context) { |
| 295 if (context == null) return false; |
| 296 |
| 297 boolean enabled = CommandLine.getInstance().hasSwitch(ChromeSwitches.ENA
BLE_DOM_DISTILLER) |
| 298 && !CommandLine.getInstance().hasSwitch( |
| 299 ApplicationSwitches.DISABLE_READER_MODE_BOTTOM_BAR) |
| 300 && !DeviceFormFactor.isTablet(context); |
| 301 if (ChromeVersionInfo.isBetaBuild() || ChromeVersionInfo.isStableBuild()
) { |
| 302 enabled = enabled |
| 303 && CommandLine.getInstance().hasSwitch( |
| 304 ApplicationSwitches.ENABLE_READER_MODE_BUTTON); |
| 305 } |
| 306 return enabled; |
| 307 } |
| 308 } |
OLD | NEW |