Index: chrome/android/java/src/org/chromium/chrome/browser/tab/TabChromeWebContentsDelegateAndroid.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabChromeWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabChromeWebContentsDelegateAndroid.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..badb1c22a75aa47a3888c91804f5ef5ff24375bd |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabChromeWebContentsDelegateAndroid.java |
@@ -0,0 +1,415 @@ |
+// Copyright 2015 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.tab; |
+ |
+import android.annotation.TargetApi; |
+import android.app.ActivityManager; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.graphics.Rect; |
+import android.media.AudioManager; |
+import android.os.Build; |
+import android.os.Handler; |
+import android.os.Message; |
+import android.util.Pair; |
+import android.view.KeyEvent; |
+import android.view.View; |
+ |
+import org.chromium.base.Log; |
+import org.chromium.base.ObserverList.RewindableIterator; |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.ChromeActivity; |
+import org.chromium.chrome.browser.ChromeApplication; |
+import org.chromium.chrome.browser.ChromeWebContentsDelegateAndroid; |
+import org.chromium.chrome.browser.RepostFormWarningDialog; |
+import org.chromium.chrome.browser.document.DocumentUtils; |
+import org.chromium.chrome.browser.document.DocumentWebContentsDelegate; |
+import org.chromium.chrome.browser.fullscreen.FullscreenManager; |
+import org.chromium.chrome.browser.media.MediaCaptureNotificationService; |
+import org.chromium.chrome.browser.policy.PolicyAuditor; |
+import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent; |
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager.TabCreator; |
+import org.chromium.chrome.browser.tabmodel.TabModel; |
+import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
+import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
+import org.chromium.chrome.browser.util.FeatureUtilities; |
+import org.chromium.content_public.browser.InvalidateTypes; |
+import org.chromium.content_public.browser.WebContents; |
+import org.chromium.ui.WindowOpenDisposition; |
+ |
+/** |
+ * A basic {@link ChromeWebContentsDelegateAndroid} that forwards some calls to the registered |
+ * {@link TabObserver}s. |
+ */ |
+public class TabChromeWebContentsDelegateAndroid |
+ extends ChromeWebContentsDelegateAndroid { |
+ /** Used for logging. */ |
+ private static final String TAG = "WebContentsDelegate"; |
+ |
+ private final Tab mTab; |
+ private final ChromeActivity mActivity; |
+ |
+ protected Handler mHandler; |
+ private final Runnable mCloseContentsRunnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ boolean isSelected = mActivity.getTabModelSelector().getCurrentTab() == mTab; |
+ mActivity.getTabModelSelector().closeTab(mTab); |
+ |
+ // If the parent Tab belongs to another Activity, fire the Intent to bring it back. |
+ if (isSelected && mTab.getParentIntent() != null |
+ && mActivity.getIntent() != mTab.getParentIntent()) { |
+ boolean mayLaunch = FeatureUtilities.isDocumentMode(mActivity) |
+ ? isParentInAndroidOverview() : true; |
+ if (mayLaunch) mActivity.startActivity(mTab.getParentIntent()); |
+ } |
+ } |
+ |
+ /** If the API allows it, returns whether a Task still exists for the parent Activity. */ |
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
+ private boolean isParentInAndroidOverview() { |
+ ActivityManager activityManager = |
+ (ActivityManager) mActivity.getSystemService(Context.ACTIVITY_SERVICE); |
+ for (ActivityManager.AppTask task : activityManager.getAppTasks()) { |
+ Intent taskIntent = DocumentUtils.getBaseIntentFromTask(task); |
+ if (taskIntent != null && taskIntent.filterEquals(mTab.getParentIntent())) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ }; |
+ |
+ public TabChromeWebContentsDelegateAndroid(Tab tab, ChromeActivity activity) { |
+ mTab = tab; |
+ mActivity = activity; |
+ |
+ mHandler = new Handler() { |
+ @Override |
+ public void handleMessage(Message msg) { |
+ if (msg == null) return; |
+ if (msg.what == Tab.MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD) { |
+ mTab.enableFullscreenAfterLoad(); |
Maria
2015/09/22 23:02:57
already being handled in tab.
aurimas (slooooooooow)
2015/09/22 23:16:49
Done
|
+ } |
+ } |
+ }; |
+ } |
+ |
+ @Override |
+ public void onLoadProgressChanged(int progress) { |
+ if (!mTab.isLoading()) return; |
+ mTab.notifyLoadProgress(mTab.getProgress()); |
+ } |
+ |
+ @Override |
+ public void onLoadStarted() { |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().onLoadStarted(mTab); |
+ } |
+ } |
+ |
+ @Override |
+ public void onLoadStopped() { |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().onLoadStopped(mTab); |
+ } |
+ } |
+ |
+ @Override |
+ public void onUpdateUrl(String url) { |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().onUpdateUrl(mTab, url); |
+ } |
+ } |
+ |
+ @Override |
+ public void showRepostFormWarningDialog() { |
+ mTab.resetSwipeRefreshHandler(); |
+ RepostFormWarningDialog warningDialog = new RepostFormWarningDialog( |
+ new Runnable() { |
+ @Override |
+ public void run() { |
+ mTab.getWebContents().getNavigationController().cancelPendingReload(); |
+ } |
+ }, new Runnable() { |
+ @Override |
+ public void run() { |
+ mTab.getWebContents().getNavigationController().continuePendingReload(); |
+ } |
+ }); |
+ warningDialog.show(mActivity.getFragmentManager(), null); |
+ } |
+ |
+ @Override |
+ public void toggleFullscreenModeForTab(boolean enableFullscreen) { |
+ if (mTab.getFullscreenManager() != null) { |
+ mTab.getFullscreenManager().setPersistentFullscreenMode(enableFullscreen); |
+ } |
+ |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().onToggleFullscreenMode(mTab, enableFullscreen); |
+ } |
+ } |
+ |
+ @Override |
+ public void navigationStateChanged(int flags) { |
+ if ((flags & InvalidateTypes.TAB) != 0) { |
+ MediaCaptureNotificationService.updateMediaNotificationForTab( |
+ mTab.getApplicationContext(), mTab.getId(), isCapturingAudio(), |
+ isCapturingVideo(), mTab.getUrl()); |
+ } |
+ if ((flags & InvalidateTypes.TITLE) != 0) { |
+ // Update cached title then notify observers. |
+ mTab.updateTitle(); |
+ } |
+ if ((flags & InvalidateTypes.URL) != 0) { |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().onUrlUpdated(mTab); |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void visibleSSLStateChanged() { |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().onSSLStateUpdated(mTab); |
+ } |
+ } |
+ |
+ @Override |
+ public void webContentsCreated(WebContents sourceWebContents, long openerRenderFrameId, |
+ String frameName, String targetUrl, WebContents newWebContents) { |
+ RewindableIterator<TabObserver> observers = mTab.getTabObservers(); |
+ while (observers.hasNext()) { |
+ observers.next().webContentsCreated(mTab, sourceWebContents, openerRenderFrameId, |
+ frameName, targetUrl, newWebContents); |
+ } |
+ // The URL can't be taken from the WebContents if it's paused. Save it for later. |
+ assert mWebContentsUrlMapping == null; |
+ mWebContentsUrlMapping = Pair.create(newWebContents, targetUrl); |
+ |
+ // TODO(dfalcantara): Re-remove this once crbug.com/508366 is fixed. |
+ TabCreator tabCreator = mActivity.getTabCreator(mTab.isIncognito()); |
+ |
+ if (tabCreator != null && tabCreator.createsTabsAsynchronously()) { |
+ DocumentWebContentsDelegate.getInstance().attachDelegate(newWebContents); |
+ } |
+ } |
+ |
+ @Override |
+ public void rendererUnresponsive() { |
+ super.rendererUnresponsive(); |
+ if (mTab.getWebContents() != null) nativeOnRendererUnresponsive(mTab.getWebContents()); |
+ if (mTab.getFullscreenManager() == null) return; |
+ mTab.mFullscreenHungRendererToken = |
+ mTab.getFullscreenManager().showControlsPersistentAndClearOldToken( |
+ mTab.mFullscreenHungRendererToken); |
+ } |
+ |
+ @Override |
+ public void rendererResponsive() { |
+ super.rendererResponsive(); |
+ if (mTab.getWebContents() != null) nativeOnRendererResponsive(mTab.getWebContents()); |
+ if (mTab.getFullscreenManager() == null) return; |
+ mTab.getFullscreenManager().hideControlsPersistent(mTab.mFullscreenHungRendererToken); |
+ mTab.mFullscreenHungRendererToken = FullscreenManager.INVALID_TOKEN; |
+ } |
+ |
+ @Override |
+ public boolean isFullscreenForTabOrPending() { |
+ return mTab.getFullscreenManager() == null |
+ ? false : mTab.getFullscreenManager().getPersistentFullscreenMode(); |
+ } |
+ |
+ @Override |
+ public void openNewTab(String url, String extraHeaders, byte[] postData, int disposition, |
+ boolean isRendererInitiated) { |
+ mTab.openNewTab(url, extraHeaders, postData, disposition, true, isRendererInitiated); |
+ } |
+ |
+ private Pair<WebContents, String> mWebContentsUrlMapping; |
+ |
+ protected TabModel getTabModel() { |
+ // TODO(dfalcantara): Remove this when DocumentActivity.getTabModelSelector() |
+ // can return a TabModelSelector that activateContents() can use. |
+ return mActivity.getTabModelSelector().getModel(mTab.isIncognito()); |
+ } |
+ |
+ @Override |
+ public boolean shouldResumeRequestsForCreatedWindow() { |
+ // Pause the WebContents if an Activity has to be created for it first. |
+ TabCreator tabCreator = mActivity.getTabCreator(mTab.isIncognito()); |
+ assert tabCreator != null; |
+ return !tabCreator.createsTabsAsynchronously(); |
+ } |
+ |
+ @Override |
+ public boolean addNewContents(WebContents sourceWebContents, WebContents webContents, |
+ int disposition, Rect initialPosition, boolean userGesture) { |
+ assert mWebContentsUrlMapping.first == webContents; |
+ |
+ TabCreator tabCreator = mActivity.getTabCreator(mTab.isIncognito()); |
+ assert tabCreator != null; |
+ |
+ // Grab the URL, which might not be available via the Tab. |
+ String url = mWebContentsUrlMapping.second; |
+ mWebContentsUrlMapping = null; |
+ |
+ // Skip opening a new Tab if it doesn't make sense. |
+ if (mTab.isClosing()) return false; |
+ |
+ // Creating new Tabs asynchronously requires starting a new Activity to create the Tab, |
+ // so the Tab returned will always be null. There's no way to know synchronously |
+ // whether the Tab is created, so assume it's always successful. |
+ boolean createdSuccessfully = tabCreator.createTabWithWebContents( |
+ webContents, mTab.getId(), TabLaunchType.FROM_LONGPRESS_FOREGROUND, url); |
+ boolean success = tabCreator.createsTabsAsynchronously() || createdSuccessfully; |
+ if (success && disposition == WindowOpenDisposition.NEW_POPUP) { |
+ PolicyAuditor auditor = |
+ ((ChromeApplication) mTab.getApplicationContext()).getPolicyAuditor(); |
+ auditor.notifyAuditEvent(mTab.getApplicationContext(), |
+ AuditEvent.OPEN_POPUP_URL_SUCCESS, url, ""); |
+ } |
+ |
+ return success; |
+ } |
+ |
+ @Override |
+ public void activateContents() { |
+ boolean activityIsDestroyed = false; |
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
+ activityIsDestroyed = mActivity.isDestroyed(); |
+ } |
+ if (activityIsDestroyed || !mTab.isInitialized()) { |
+ Log.e(TAG, "Activity destroyed before calling activateContents(). Bailing out."); |
+ return; |
+ } |
+ |
+ TabModel model = getTabModel(); |
+ int index = model.indexOf(mTab); |
+ if (index == TabModel.INVALID_TAB_INDEX) return; |
+ TabModelUtils.setIndex(model, index); |
+ bringActivityToForeground(); |
+ } |
+ |
+ /** |
+ * Brings chrome's Activity to foreground, if it is not so. |
+ */ |
+ protected void bringActivityToForeground() { |
+ // This intent is sent in order to get the activity back to the foreground if it was |
+ // not already. The previous call will activate the right tab in the context of the |
+ // TabModel but will only show the tab to the user if Chrome was already in the |
+ // foreground. |
+ // The intent is getting the tabId mostly because it does not cost much to do so. |
+ // When receiving the intent, the tab associated with the tabId should already be |
+ // active. |
+ // Note that calling only the intent in order to activate the tab is slightly slower |
+ // because it will change the tab when the intent is handled, which happens after |
+ // Chrome gets back to the foreground. |
+ Intent newIntent = Tab.createBringTabToFrontIntent(mTab.getId()); |
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ |
+ mTab.getApplicationContext().startActivity(newIntent); |
+ } |
+ |
+ @Override |
+ public void closeContents() { |
+ // Execute outside of callback, otherwise we end up deleting the native |
+ // objects in the middle of executing methods on them. |
+ mHandler.removeCallbacks(mCloseContentsRunnable); |
+ mHandler.post(mCloseContentsRunnable); |
+ } |
+ |
+ @Override |
+ public boolean takeFocus(boolean reverse) { |
+ if (reverse) { |
+ View menuButton = mActivity.findViewById(R.id.menu_button); |
+ if (menuButton == null || !menuButton.isShown()) { |
+ menuButton = mActivity.findViewById(R.id.document_menu_button); |
+ } |
+ if (menuButton != null && menuButton.isShown()) { |
+ return menuButton.requestFocus(); |
+ } |
+ |
+ View tabSwitcherButton = mActivity.findViewById(R.id.tab_switcher_button); |
+ if (tabSwitcherButton != null && tabSwitcherButton.isShown()) { |
+ return tabSwitcherButton.requestFocus(); |
+ } |
+ } else { |
+ View urlBar = mActivity.findViewById(R.id.url_bar); |
+ if (urlBar != null) return urlBar.requestFocus(); |
+ } |
+ return false; |
+ } |
+ |
+ @Override |
+ public void handleKeyboardEvent(KeyEvent event) { |
+ if (event.getAction() == KeyEvent.ACTION_DOWN) { |
+ if (mActivity.onKeyDown(event.getKeyCode(), event)) return; |
+ |
+ // Handle the Escape key here (instead of in KeyboardShortcuts.java), so it doesn't |
+ // interfere with other parts of the activity (e.g. the URL bar). |
+ if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) { |
+ WebContents wc = mTab.getWebContents(); |
+ if (wc != null) wc.stop(); |
+ return; |
+ } |
+ } |
+ handleMediaKey(event); |
+ } |
+ |
+ /** |
+ * Redispatches unhandled media keys. This allows bluetooth headphones with play/pause or |
+ * other buttons to function correctly. |
+ */ |
+ @TargetApi(19) |
+ private void handleMediaKey(KeyEvent e) { |
+ if (Build.VERSION.SDK_INT < 19) return; |
+ switch (e.getKeyCode()) { |
+ case KeyEvent.KEYCODE_MUTE: |
+ case KeyEvent.KEYCODE_HEADSETHOOK: |
+ case KeyEvent.KEYCODE_MEDIA_PLAY: |
+ case KeyEvent.KEYCODE_MEDIA_PAUSE: |
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
+ case KeyEvent.KEYCODE_MEDIA_STOP: |
+ case KeyEvent.KEYCODE_MEDIA_NEXT: |
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
+ case KeyEvent.KEYCODE_MEDIA_REWIND: |
+ case KeyEvent.KEYCODE_MEDIA_RECORD: |
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: |
+ case KeyEvent.KEYCODE_MEDIA_CLOSE: |
+ case KeyEvent.KEYCODE_MEDIA_EJECT: |
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: |
+ AudioManager am = (AudioManager) mActivity.getSystemService( |
+ Context.AUDIO_SERVICE); |
+ am.dispatchMediaKeyEvent(e); |
+ break; |
+ default: |
+ break; |
+ } |
+ } |
+ |
+ /** |
+ * @return Whether audio is being captured. |
+ */ |
+ private boolean isCapturingAudio() { |
+ return !mTab.isClosing() |
+ && ChromeWebContentsDelegateAndroid.nativeIsCapturingAudio(mTab.getWebContents()); |
+ } |
+ |
+ /** |
+ * @return Whether video is being captured. |
+ */ |
+ private boolean isCapturingVideo() { |
+ return !mTab.isClosing() |
+ && ChromeWebContentsDelegateAndroid.nativeIsCapturingVideo(mTab.getWebContents()); |
+ } |
+} |