blob: 576fb43e57699201706ccf912b66be7c48107faa [file] [log] [blame]
// Copyright 2016 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.vr_shell;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.StrictMode;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.FrameLayout;
import com.google.vr.ndk.base.AndroidCompat;
import com.google.vr.ndk.base.GvrLayout;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.NativePage;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.InterceptNavigationDelegateImpl;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabRedirectHandler;
import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
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.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.browser.MotionEventSynthesizer;
import org.chromium.content.browser.WindowAndroidChangedObserver;
import org.chromium.content.browser.WindowAndroidProvider;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.BrowserControlsState;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.display.VirtualDisplayAndroid;
/**
* This view extends from GvrLayout which wraps a GLSurfaceView that renders VR shell.
*/
@JNINamespace("vr_shell")
public class VrShellImpl
extends GvrLayout implements VrShell, SurfaceHolder.Callback, WindowAndroidProvider {
private static final String TAG = "VrShellImpl";
// TODO(mthiesse): These values work well for Pixel/Pixel XL in VR, but we need to come up with
// a way to compute good values for any screen size/scaling ratio.
// Increasing DPR any more than this doesn't appear to increase text quality.
private static final float DEFAULT_DPR = 1.4f;
// Fairly arbitrary values that put a good amount of content on the screen without making the
// text too small to read.
@VisibleForTesting
public static final float DEFAULT_CONTENT_WIDTH = 800f;
@VisibleForTesting
public static final float DEFAULT_CONTENT_HEIGHT = 533f;
// Make full screen 16:9 until we get exact dimensions from playing video.
private static final float FULLSCREEN_CONTENT_WIDTH = 1024f;
private static final float FULLSCREEN_CONTENT_HEIGHT = 576f;
private final ChromeActivity mActivity;
private final VrShellDelegate mDelegate;
private final VirtualDisplayAndroid mContentVirtualDisplay;
private final InterceptNavigationDelegateImpl mInterceptNavigationDelegate;
private final TabRedirectHandler mTabRedirectHandler;
private final TabObserver mTabObserver;
private final TabModelSelectorObserver mTabModelSelectorObserver;
private final View.OnTouchListener mTouchListener;
private TabModelSelectorTabObserver mTabModelSelectorTabObserver;
private long mNativeVrShell;
private FrameLayout mRenderToSurfaceLayoutParent;
private FrameLayout mRenderToSurfaceLayout;
private Surface mSurface;
private View mPresentationView;
// The tab that holds the main ContentViewCore.
private Tab mTab;
private ContentViewCore mContentViewCore;
private NativePage mNativePage;
private Boolean mCanGoBack;
private Boolean mCanGoForward;
private VrWindowAndroid mContentVrWindowAndroid;
private boolean mReprojectedRendering;
private InterceptNavigationDelegateImpl mNonVrInterceptNavigationDelegate;
private TabRedirectHandler mNonVrTabRedirectHandler;
private TabModelSelector mTabModelSelector;
private float mLastContentWidth;
private float mLastContentHeight;
private float mLastContentDpr;
private Boolean mPaused;
private MotionEventSynthesizer mMotionEventSynthesizer;
private OnDispatchTouchEventCallback mOnDispatchTouchEventForTesting;
public VrShellImpl(
ChromeActivity activity, VrShellDelegate delegate, TabModelSelector tabModelSelector) {
super(activity);
mActivity = activity;
mDelegate = delegate;
mTabModelSelector = tabModelSelector;
// This overrides the default intent created by GVR to return to Chrome when the DON flow
// is triggered by resuming the GvrLayout, which is the usual way Daydream apps enter VR.
// See VrShellDelegate#getEnterVrPendingIntent for why we need to do this.
setReentryIntent(VrShellDelegate.getEnterVrPendingIntent(activity));
mReprojectedRendering = setAsyncReprojectionEnabled(true);
if (mReprojectedRendering) {
// No need render to a Surface if we're reprojected. We'll be rendering with surfaceless
// EGL.
mPresentationView = new FrameLayout(mActivity);
// Only enable sustained performance mode when Async reprojection decouples the app
// framerate from the display framerate.
AndroidCompat.setSustainedPerformanceMode(mActivity, true);
} else {
SurfaceView surfaceView = new SurfaceView(mActivity);
surfaceView.getHolder().addCallback(this);
mPresentationView = surfaceView;
}
setPresentationView(mPresentationView);
getUiLayout().setCloseButtonListener(new Runnable() {
@Override
public void run() {
mDelegate.shutdownVr(true /* disableVrMode */, false /* canReenter */,
true /* stayingInChrome */);
}
});
DisplayAndroid primaryDisplay = DisplayAndroid.getNonMultiDisplay(activity);
mContentVirtualDisplay = VirtualDisplayAndroid.createVirtualDisplay();
mContentVirtualDisplay.setTo(primaryDisplay);
mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(
new VrExternalNavigationDelegate(mActivity.getActivityTab()),
mActivity.getActivityTab());
mTabRedirectHandler = new TabRedirectHandler(mActivity) {
@Override
public boolean shouldStayInChrome(boolean hasExternalProtocol) {
return true;
}
};
mTabObserver = new EmptyTabObserver() {
@Override
public void onContentChanged(Tab tab) {
// Restore proper focus on the old CVC.
if (mContentViewCore != null) mContentViewCore.onWindowFocusChanged(false);
mContentViewCore = null;
if (mNativeVrShell == 0) return;
if (tab.isShowingSadTab()) {
// For now we don't support the sad tab page. crbug.com/661609.
forceExitVr();
return;
}
if (mNativePage != null) {
UiUtils.removeViewFromParent(mNativePage.getView());
mNativePage = null;
mMotionEventSynthesizer = null;
if (tab.getNativePage() == null) {
nativeRestoreContentSurface(mNativeVrShell);
mRenderToSurfaceLayoutParent.setVisibility(View.INVISIBLE);
mSurface = null;
}
}
if (tab.getNativePage() != null) {
mRenderToSurfaceLayoutParent.setVisibility(View.VISIBLE);
mNativePage = tab.getNativePage();
if (mSurface == null) mSurface = nativeTakeContentSurface(mNativeVrShell);
mRenderToSurfaceLayout.addView(mNativePage.getView(),
new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mNativePage.getView().invalidate();
mMotionEventSynthesizer =
new MotionEventSynthesizer(mRenderToSurfaceLayout, VrShellImpl.this);
}
setContentCssSize(mLastContentWidth, mLastContentHeight, mLastContentDpr);
if (tab.getNativePage() == null && tab.getContentViewCore() != null) {
mContentViewCore = tab.getContentViewCore();
mContentViewCore.onAttachedToWindow();
mContentViewCore.getContainerView().requestFocus();
// We need the CVC to think it has Window Focus so it doesn't blur the page,
// even though we're drawing VR layouts over top of it.
mContentViewCore.onWindowFocusChanged(true);
nativeSwapContents(mNativeVrShell, mContentViewCore.getWebContents(), null);
} else {
nativeSwapContents(mNativeVrShell, null, mMotionEventSynthesizer);
}
updateHistoryButtonsVisibility();
}
@Override
public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) {
onContentChanged(tab);
}
@Override
public void onLoadProgressChanged(Tab tab, int progress) {
if (mNativeVrShell == 0) return;
nativeOnLoadProgressChanged(mNativeVrShell, progress / 100.0);
}
@Override
public void onCrash(Tab tab, boolean sadTabShown) {
updateHistoryButtonsVisibility();
}
@Override
public void onLoadStarted(Tab tab, boolean toDifferentDocument) {
if (!toDifferentDocument) return;
updateHistoryButtonsVisibility();
}
@Override
public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
if (!toDifferentDocument) return;
updateHistoryButtonsVisibility();
}
@Override
public void onUrlUpdated(Tab tab) {
updateHistoryButtonsVisibility();
}
};
mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() {
@Override
public void onChange() {
swapToForegroundTab();
}
@Override
public void onNewTabCreated(Tab tab) {
if (mNativeVrShell == 0) return;
nativeOnTabUpdated(mNativeVrShell, tab.isIncognito(), tab.getId(), tab.getTitle());
}
};
mTouchListener = new View.OnTouchListener() {
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
nativeOnTriggerEvent(mNativeVrShell, true);
return true;
} else if (event.getActionMasked() == MotionEvent.ACTION_UP
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
nativeOnTriggerEvent(mNativeVrShell, false);
return true;
}
return false;
}
};
// We need a parent for the RenderToSurfaceLayout because we want screen taps to only be
// routed to the GvrUiLayout, and not propagate through to the NativePage. So screen taps
// fall through the RenderToSurfaceLayoutParent, onto the GvrUiLayout, while touch events
// generated from the VR controller are injected directly into the RenderToSurfaceLayout,
// bypassing the parent.
mRenderToSurfaceLayoutParent = new FrameLayout(mActivity) {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return false;
}
};
mRenderToSurfaceLayoutParent.setVisibility(View.INVISIBLE);
mRenderToSurfaceLayout = new FrameLayout(mActivity) {
@Override
protected void dispatchDraw(Canvas canvas) {
if (mSurface == null) return;
// TODO(mthiesse): Support mSurface.lockHardwareCanvas(); crbug.com/692775
final Canvas surfaceCanvas = mSurface.lockCanvas(null);
super.dispatchDraw(surfaceCanvas);
mSurface.unlockCanvasAndPost(surfaceCanvas);
}
};
mRenderToSurfaceLayout.setVisibility(View.VISIBLE);
// We need a pre-draw listener to invalidate the native page because scrolling usually
// doesn't trigger an onDraw call, so our texture won't get updated.
mRenderToSurfaceLayout.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (mRenderToSurfaceLayout.isDirty()) {
mRenderToSurfaceLayout.invalidate();
if (mNativePage != null) mNativePage.getView().invalidate();
}
return true;
}
});
mRenderToSurfaceLayoutParent.addView(mRenderToSurfaceLayout);
addView(mRenderToSurfaceLayoutParent);
}
private void setSplashScreenIcon() {
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Drawable drawable = ApiCompatibilityUtils.getDrawable(
mActivity.getResources(), R.mipmap.app_icon);
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bd = (BitmapDrawable) drawable;
return bd.getBitmap();
}
assert false : "The drawable was not a bitmap drawable as expected";
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (mNativeVrShell == 0) return;
nativeSetSplashScreenIcon(mNativeVrShell, bitmap);
}
}
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void initializeNative(Tab currentTab, boolean forWebVr,
boolean webVrAutopresentationExpected, boolean inCct) {
mContentVrWindowAndroid = new VrWindowAndroid(mActivity, mContentVirtualDisplay);
mNativeVrShell = nativeInit(mDelegate, mContentVrWindowAndroid.getNativePointer(), forWebVr,
webVrAutopresentationExpected, inCct, getGvrApi().getNativeGvrContext(),
mReprojectedRendering);
// We need to set the icon bitmap from here because we can't read the app icon from native
// code.
setSplashScreenIcon();
// Set the UI and content sizes before we load the UI.
setContentCssSize(DEFAULT_CONTENT_WIDTH, DEFAULT_CONTENT_HEIGHT, DEFAULT_DPR);
reparentAllTabs(mContentVrWindowAndroid);
swapToForegroundTab();
createTabList();
mActivity.getTabModelSelector().addObserver(mTabModelSelectorObserver);
createTabModelSelectorTabObserver();
updateHistoryButtonsVisibility();
mPresentationView.setOnTouchListener(mTouchListener);
}
private void createTabList() {
assert mNativeVrShell != 0;
TabModel main = mTabModelSelector.getModel(false);
int count = main.getCount();
Tab[] mainTabs = new Tab[count];
for (int i = 0; i < count; ++i) {
mainTabs[i] = main.getTabAt(i);
}
TabModel incognito = mTabModelSelector.getModel(true);
count = incognito.getCount();
Tab[] incognitoTabs = new Tab[count];
for (int i = 0; i < count; ++i) {
incognitoTabs[i] = incognito.getTabAt(i);
}
nativeOnTabListCreated(mNativeVrShell, mainTabs, incognitoTabs);
}
private void swapToForegroundTab() {
Tab tab = mActivity.getActivityTab();
if (tab == mTab) return;
if (!mDelegate.canEnterVr(tab, false)) {
forceExitVr();
return;
}
if (mTab != null) {
mTab.removeObserver(mTabObserver);
restoreTabFromVR();
mTab.updateFullscreenEnabledState();
}
mTab = tab;
initializeTabForVR();
mTab.addObserver(mTabObserver);
mTab.updateFullscreenEnabledState();
mTabObserver.onContentChanged(mTab);
}
private void initializeTabForVR() {
mNonVrInterceptNavigationDelegate = mTab.getInterceptNavigationDelegate();
mTab.setInterceptNavigationDelegate(mInterceptNavigationDelegate);
// Make sure we are not redirecting to another app, i.e. out of VR mode.
mNonVrTabRedirectHandler = mTab.getTabRedirectHandler();
mTab.setTabRedirectHandler(mTabRedirectHandler);
assert mTab.getWindowAndroid() == mContentVrWindowAndroid;
}
private void restoreTabFromVR() {
mTab.setInterceptNavigationDelegate(mNonVrInterceptNavigationDelegate);
mTab.setTabRedirectHandler(mNonVrTabRedirectHandler);
mNonVrTabRedirectHandler = null;
}
private void reparentAllTabs(WindowAndroid window) {
// Ensure new tabs are created with the correct window.
boolean[] values = {true, false};
for (boolean incognito : values) {
TabCreator tabCreator = mActivity.getTabCreator(incognito);
if (tabCreator instanceof ChromeTabCreator) {
((ChromeTabCreator) tabCreator).setWindowAndroid(window);
}
}
// Reparent all existing tabs.
for (TabModel model : mActivity.getTabModelSelector().getModels()) {
for (int i = 0; i < model.getCount(); ++i) {
model.getTabAt(i).updateWindowAndroid(window);
}
}
}
// Exits VR, telling the user to remove their headset, and returning to Chromium.
@CalledByNative
public void forceExitVr() {
VrShellDelegate.showDoffAndExitVr(false);
}
// Called because showing PageInfo isn't supported in VR. This happens when the user clicks on
// the security icon in the URL bar.
@CalledByNative
public void onUnhandledPageInfo() {
mDelegate.onUnhandledPageInfo();
}
// Exits CCT, returning to the app that opened it.
@CalledByNative
public void exitCct() {
mDelegate.exitCct();
}
@CalledByNative
public void setContentCssSize(float width, float height, float dpr) {
ThreadUtils.assertOnUiThread();
mLastContentWidth = width;
mLastContentHeight = height;
mLastContentDpr = dpr;
if (mNativePage != null) {
// Native pages don't listen to our DPR changes, so to get them to render at the correct
// size we need to make them larger.
DisplayAndroid primaryDisplay = DisplayAndroid.getNonMultiDisplay(mActivity);
float dip = primaryDisplay.getDipScale();
width *= (dip / dpr);
height *= (dip / dpr);
}
int surfaceWidth = (int) Math.ceil(width * dpr);
int surfaceHeight = (int) Math.ceil(height * dpr);
Point size = new Point(surfaceWidth, surfaceHeight);
mContentVirtualDisplay.update(size, dpr, null, null, null);
if (mTab != null && mTab.getContentViewCore() != null) {
mTab.getContentViewCore().onSizeChanged(surfaceWidth, surfaceHeight, 0, 0);
nativeOnPhysicalBackingSizeChanged(mNativeVrShell,
mTab.getContentViewCore().getWebContents(), surfaceWidth, surfaceHeight);
}
mRenderToSurfaceLayout.setLayoutParams(
new FrameLayout.LayoutParams(surfaceWidth, surfaceHeight));
nativeContentPhysicalBoundsChanged(mNativeVrShell, surfaceWidth, surfaceHeight, dpr);
}
@CalledByNative
public void onFullscreenChanged(boolean enabled) {
if (enabled) {
setContentCssSize(FULLSCREEN_CONTENT_WIDTH, FULLSCREEN_CONTENT_HEIGHT, DEFAULT_DPR);
} else {
setContentCssSize(DEFAULT_CONTENT_WIDTH, DEFAULT_CONTENT_HEIGHT, DEFAULT_DPR);
}
}
@CalledByNative
public void contentSurfaceChanged() {
if (mSurface != null || mNativePage == null) return;
mSurface = nativeTakeContentSurface(mNativeVrShell);
mNativePage.getView().invalidate();
mRenderToSurfaceLayout.invalidate();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean parentConsumed = super.dispatchTouchEvent(event);
if (mOnDispatchTouchEventForTesting != null) {
mOnDispatchTouchEventForTesting.onDispatchTouchEvent(parentConsumed);
}
return parentConsumed;
}
@Override
public void onResume() {
if (mPaused != null && !mPaused) return;
mPaused = false;
super.onResume();
if (mNativeVrShell != 0) {
// Refreshing the viewer profile may accesses disk under some circumstances outside of
// our control.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
nativeOnResume(mNativeVrShell);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
}
@Override
public void onPause() {
if (mPaused != null && mPaused) return;
mPaused = true;
super.onPause();
if (mNativeVrShell != 0) {
nativeOnPause(mNativeVrShell);
}
}
@Override
public void shutdown() {
reparentAllTabs(mActivity.getWindowAndroid());
if (mNativeVrShell != 0) {
nativeDestroy(mNativeVrShell);
mNativeVrShell = 0;
}
if (mNativePage != null) UiUtils.removeViewFromParent(mNativePage.getView());
mTabModelSelector.removeObserver(mTabModelSelectorObserver);
mTabModelSelectorTabObserver.destroy();
mTab.removeObserver(mTabObserver);
restoreTabFromVR();
if (mTab != null) {
mTab.updateBrowserControlsState(BrowserControlsState.SHOWN, true);
}
mContentVirtualDisplay.destroy();
super.shutdown();
}
@Override
public void pause() {
onPause();
}
@Override
public void resume() {
onResume();
}
@Override
public void teardown() {
shutdown();
}
@Override
public void setWebVrModeEnabled(boolean enabled, boolean showToast) {
mContentVrWindowAndroid.setVSyncPaused(enabled);
nativeSetWebVrMode(mNativeVrShell, enabled, showToast);
}
@Override
public boolean getWebVrModeEnabled() {
return nativeGetWebVrMode(mNativeVrShell);
}
@Override
public boolean isDisplayingUrlForTesting() {
return nativeIsDisplayingUrlForTesting(mNativeVrShell);
}
@Override
public FrameLayout getContainer() {
return this;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
nativeSetSurface(mNativeVrShell, holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// No need to do anything here, we don't care about surface width/height.
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO(mthiesse): For now we don't need to handle this because we exit VR on activity pause
// (which destroys the surface). If in the future we don't destroy VR Shell on exiting,
// we will need to handle this, or at least properly handle surfaceCreated being called
// multiple times.
}
private void createTabModelSelectorTabObserver() {
assert mTabModelSelectorTabObserver == null;
mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(mTabModelSelector) {
@Override
public void onTitleUpdated(Tab tab) {
if (mNativeVrShell == 0) return;
nativeOnTabUpdated(mNativeVrShell, tab.isIncognito(), tab.getId(), tab.getTitle());
}
@Override
public void onClosingStateChanged(Tab tab, boolean closing) {
if (mNativeVrShell == 0) return;
if (closing) {
nativeOnTabRemoved(mNativeVrShell, tab.isIncognito(), tab.getId());
} else {
nativeOnTabUpdated(mNativeVrShell, tab.isIncognito(), tab.getId(),
tab.getTitle());
}
}
@Override
public void onDestroyed(Tab tab) {
if (mNativeVrShell == 0) return;
nativeOnTabRemoved(mNativeVrShell, tab.isIncognito(), tab.getId());
}
};
}
@CalledByNative
public boolean hasDaydreamSupport() {
return mDelegate.hasDaydreamSupport();
}
@CalledByNative
private void showTab(int id) {
Tab tab = mActivity.getTabModelSelector().getTabById(id);
if (tab == null) {
return;
}
int index = mActivity.getTabModelSelector().getModel(tab.isIncognito()).indexOf(tab);
if (index == TabModel.INVALID_TAB_INDEX) {
return;
}
TabModelUtils.setIndex(mActivity.getTabModelSelector().getModel(tab.isIncognito()), index);
}
@CalledByNative
private void openNewTab(boolean incognito) {
mActivity.getTabCreator(incognito).launchUrl(
UrlConstants.NTP_URL, TabLaunchType.FROM_CHROME_UI);
}
@CalledByNative
public void navigateForward() {
mActivity.getToolbarManager().forward();
updateHistoryButtonsVisibility();
}
@CalledByNative
public void navigateBack() {
if (mActivity instanceof ChromeTabbedActivity) {
// TODO(mthiesse): We should do this for custom tabs as well, as back for custom tabs
// is also expected to close tabs.
((ChromeTabbedActivity) mActivity).handleBackPressed();
} else {
mActivity.getToolbarManager().back();
}
updateHistoryButtonsVisibility();
}
private void updateHistoryButtonsVisibility() {
if (mTab == null) {
nativeSetHistoryButtonsEnabled(mNativeVrShell, false, false);
return;
}
// Hitting back when on the NTP usually closes Chrome, which we don't allow in VR, so we
// just disable the back button.
boolean shouldAlwaysGoBack = mActivity instanceof ChromeTabbedActivity
&& (mNativePage == null || !(mNativePage instanceof NewTabPage));
boolean canGoBack = mTab.canGoBack() || shouldAlwaysGoBack;
boolean canGoForward = mTab.canGoForward();
if ((mCanGoBack != null && canGoBack == mCanGoBack)
&& (mCanGoForward != null && canGoForward == mCanGoForward)) {
return;
}
mCanGoBack = canGoBack;
mCanGoForward = canGoForward;
nativeSetHistoryButtonsEnabled(mNativeVrShell, mCanGoBack, mCanGoForward);
}
@CalledByNative
public void reload() {
mTab.reload();
}
@CalledByNative
public float getNativePageScrollRatio() {
return mActivity.getWindowAndroid().getDisplay().getDipScale()
/ mContentVrWindowAndroid.getDisplay().getDipScale();
}
@Override
public WindowAndroid getWindowAndroid() {
return mContentVrWindowAndroid;
}
@Override
public void addWindowAndroidChangedObserver(WindowAndroidChangedObserver observer) {}
@Override
public void removeWindowAndroidChangedObserver(WindowAndroidChangedObserver observer) {}
/**
* Sets the callback that will be run when VrShellImpl's dispatchTouchEvent
* is run and the parent consumed the event.
* @param callback The Callback to be run.
*/
@VisibleForTesting
public void setOnDispatchTouchEventForTesting(OnDispatchTouchEventCallback callback) {
mOnDispatchTouchEventForTesting = callback;
}
private native long nativeInit(VrShellDelegate delegate, long nativeWindowAndroid,
boolean forWebVR, boolean webVrAutopresentationExpected, boolean inCct, long gvrApi,
boolean reprojectedRendering);
private native void nativeSetSurface(long nativeVrShell, Surface surface);
private native void nativeSetSplashScreenIcon(long nativeVrShell, Bitmap bitmap);
private native void nativeSwapContents(
long nativeVrShell, WebContents webContents, MotionEventSynthesizer eventSynthesizer);
private native void nativeDestroy(long nativeVrShell);
private native void nativeOnTriggerEvent(long nativeVrShell, boolean touched);
private native void nativeOnPause(long nativeVrShell);
private native void nativeOnResume(long nativeVrShell);
private native void nativeOnLoadProgressChanged(long nativeVrShell, double progress);
private native void nativeOnPhysicalBackingSizeChanged(
long nativeVrShell, WebContents webContents, int width, int height);
private native void nativeContentPhysicalBoundsChanged(long nativeVrShell, int width,
int height, float dpr);
private native void nativeSetWebVrMode(long nativeVrShell, boolean enabled, boolean showToast);
private native boolean nativeGetWebVrMode(long nativeVrShell);
private native boolean nativeIsDisplayingUrlForTesting(long nativeVrShell);
private native void nativeOnTabListCreated(long nativeVrShell, Tab[] mainTabs,
Tab[] incognitoTabs);
private native void nativeOnTabUpdated(long nativeVrShell, boolean incognito, int id,
String title);
private native void nativeOnTabRemoved(long nativeVrShell, boolean incognito, int id);
private native Surface nativeTakeContentSurface(long nativeVrShell);
private native void nativeRestoreContentSurface(long nativeVrShell);
private native void nativeSetHistoryButtonsEnabled(
long nativeVrShell, boolean canGoBack, boolean canGoForward);
}