1 // Copyright 2016 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.vr; 6 7 import android.annotation.SuppressLint; 8 import android.annotation.TargetApi; 9 import android.content.pm.PackageManager; 10 import android.graphics.Color; 11 import android.graphics.PixelFormat; 12 import android.graphics.Point; 13 import android.graphics.PointF; 14 import android.os.Build; 15 import android.util.DisplayMetrics; 16 import android.view.KeyEvent; 17 import android.view.MotionEvent; 18 import android.view.Surface; 19 import android.view.SurfaceHolder; 20 import android.view.SurfaceView; 21 import android.view.View; 22 import android.view.ViewConfiguration; 23 import android.view.ViewGroup; 24 import android.widget.FrameLayout; 25 26 import androidx.annotation.VisibleForTesting; 27 28 import com.google.vr.ndk.base.AndroidCompat; 29 import com.google.vr.ndk.base.GvrLayout; 30 31 import org.chromium.base.Log; 32 import org.chromium.base.StrictModeContext; 33 import org.chromium.base.ThreadUtils; 34 import org.chromium.base.annotations.CalledByNative; 35 import org.chromium.base.annotations.JNINamespace; 36 import org.chromium.base.annotations.NativeMethods; 37 import org.chromium.base.supplier.ObservableSupplierImpl; 38 import org.chromium.base.task.PostTask; 39 import org.chromium.chrome.R; 40 import org.chromium.chrome.browser.ChromeTabbedActivity; 41 import org.chromium.chrome.browser.app.ChromeActivity; 42 import org.chromium.chrome.browser.compositor.CompositorView; 43 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; 44 import org.chromium.chrome.browser.page_info.ChromePageInfoControllerDelegate; 45 import org.chromium.chrome.browser.page_info.ChromePermissionParamsListBuilderDelegate; 46 import org.chromium.chrome.browser.tab.EmptyTabObserver; 47 import org.chromium.chrome.browser.tab.RedirectHandlerTabHelper; 48 import org.chromium.chrome.browser.tab.Tab; 49 import org.chromium.chrome.browser.tab.TabAssociatedApp; 50 import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper; 51 import org.chromium.chrome.browser.tab.TabCreationState; 52 import org.chromium.chrome.browser.tab.TabLaunchType; 53 import org.chromium.chrome.browser.tab.TabObserver; 54 import org.chromium.chrome.browser.tabmodel.ChromeTabCreator; 55 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; 56 import org.chromium.chrome.browser.tabmodel.TabCreator; 57 import org.chromium.chrome.browser.tabmodel.TabModel; 58 import org.chromium.chrome.browser.tabmodel.TabModelSelector; 59 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; 60 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver; 61 import org.chromium.chrome.browser.util.VoiceRecognitionUtil; 62 import org.chromium.chrome.browser.vr.keyboard.VrInputMethodManagerWrapper; 63 import org.chromium.components.external_intents.RedirectHandler; 64 import org.chromium.components.page_info.PageInfoController; 65 import org.chromium.content_public.browser.ImeAdapter; 66 import org.chromium.content_public.browser.LoadUrlParams; 67 import org.chromium.content_public.browser.UiThreadTaskTraits; 68 import org.chromium.content_public.browser.ViewEventSink; 69 import org.chromium.content_public.browser.WebContents; 70 import org.chromium.content_public.common.BrowserControlsState; 71 import org.chromium.ui.base.PermissionCallback; 72 import org.chromium.ui.base.WindowAndroid; 73 import org.chromium.ui.display.DisplayAndroid; 74 import org.chromium.ui.display.DisplayAndroidManager; 75 import org.chromium.ui.display.VirtualDisplayAndroid; 76 import org.chromium.ui.modaldialog.DialogDismissalCause; 77 import org.chromium.ui.modaldialog.ModalDialogManager; 78 import org.chromium.ui.widget.UiWidgetFactory; 79 80 import java.util.ArrayList; 81 82 /** 83 * This view extends from GvrLayout which wraps a GLSurfaceView that renders VR shell. 84 */ 85 @JNINamespace("vr") 86 public class VrShell extends GvrLayout 87 implements SurfaceHolder.Callback, VrInputMethodManagerWrapper.BrowserKeyboardInterface, 88 EmptySniffingVrViewContainer.EmptyListener, VrDialogManager, VrToastManager { 89 private static final String TAG = "VrShellImpl"; 90 private static final float INCHES_TO_METERS = 0.0254f; 91 92 private final ChromeActivity mActivity; 93 private final CompositorView mCompositorView; 94 private final VrCompositorSurfaceManager mVrCompositorSurfaceManager; 95 private final VrShellDelegate mDelegate; 96 private final VirtualDisplayAndroid mContentVirtualDisplay; 97 private final RedirectHandler mRedirectHandler; 98 private final TabObserver mTabObserver; 99 private final TabModelSelectorObserver mTabModelSelectorObserver; 100 private final View.OnTouchListener mTouchListener; 101 private final boolean mVrBrowsingEnabled; 102 103 private TabModelSelectorTabObserver mTabModelSelectorTabObserver; 104 105 private long mNativeVrShell; 106 107 private View mPresentationView; 108 109 // The tab that holds the main WebContents. 110 private Tab mTab; 111 private ViewEventSink mViewEventSink; 112 private Boolean mCanGoBack; 113 private Boolean mCanGoForward; 114 115 private VrWindowAndroid mContentVrWindowAndroid; 116 117 private boolean mReprojectedRendering; 118 119 private RedirectHandler mNonVrRedirectHandler; 120 private UiWidgetFactory mNonVrUiWidgetFactory; 121 122 private TabModelSelector mTabModelSelector; 123 private float mLastContentWidth; 124 private float mLastContentHeight; 125 private float mLastContentDpr; 126 private Boolean mPaused; 127 128 private boolean mPendingVSyncPause; 129 130 private AndroidUiGestureTarget mAndroidUiGestureTarget; 131 private AndroidUiGestureTarget mAndroidDialogGestureTarget; 132 133 private OnDispatchTouchEventCallback mOnDispatchTouchEventForTesting; 134 private Runnable mOnVSyncPausedForTesting; 135 136 private Surface mContentSurface; 137 private EmptySniffingVrViewContainer mNonVrViews; 138 private VrViewContainer mVrUiViewContainer; 139 private FrameLayout mUiView; 140 private ModalDialogManager mNonVrModalDialogManager; 141 private ModalDialogManager mVrModalDialogManager; 142 private VrModalPresenter mVrModalPresenter; 143 private Runnable mVrDialogDismissHandler; 144 145 private VrInputMethodManagerWrapper mInputMethodManagerWrapper; 146 147 private ArrayList<Integer> mUiOperationResults; 148 private ArrayList<Runnable> mUiOperationResultCallbacks; 149 150 /** 151 * A struct-like object for registering UI operations during tests. 152 */ 153 @VisibleForTesting 154 public static class UiOperationData { 155 // The UiTestOperationType of this operation. 156 public int actionType; 157 // The callback to run when the operation completes. 158 public Runnable resultCallback; 159 // The timeout of the operation. 160 public int timeoutMs; 161 // The UserFriendlyElementName to perform the operation on. 162 public int elementName; 163 // The desired visibility status of the element. 164 public boolean visibility; 165 } 166 VrShell( ChromeActivity activity, VrShellDelegate delegate, TabModelSelector tabModelSelector)167 public VrShell( 168 ChromeActivity activity, VrShellDelegate delegate, TabModelSelector tabModelSelector) { 169 super(activity); 170 mActivity = activity; 171 mDelegate = delegate; 172 mTabModelSelector = tabModelSelector; 173 mVrBrowsingEnabled = mDelegate.isVrBrowsingEnabled(); 174 175 mReprojectedRendering = setAsyncReprojectionEnabled(true); 176 if (mReprojectedRendering) { 177 // No need render to a Surface if we're reprojected. We'll be rendering with surfaceless 178 // EGL. 179 mPresentationView = new FrameLayout(mActivity); 180 181 // This can show up behind popups on standalone devices, so make sure it's black. 182 mPresentationView.setBackgroundColor(Color.BLACK); 183 184 // Only enable sustained performance mode when Async reprojection decouples the app 185 // framerate from the display framerate. 186 AndroidCompat.setSustainedPerformanceMode(mActivity, true); 187 } else { 188 if (VrShellDelegate.isDaydreamCurrentViewer()) { 189 // We need Async Reprojection on when entering VR browsing, because otherwise our 190 // GL context will be lost every time we're hidden, like when we go to the dashboard 191 // and come back. 192 // TODO(mthiesse): Supporting context loss turned out to be hard. We should consider 193 // spending more effort on supporting this in the future if it turns out to be 194 // important. 195 Log.e(TAG, "Could not turn async reprojection on for Daydream headset."); 196 throw new VrShellDelegate.VrUnsupportedException(); 197 } 198 SurfaceView surfaceView = new SurfaceView(mActivity); 199 surfaceView.getHolder().addCallback(this); 200 mPresentationView = surfaceView; 201 } 202 203 mActivity.getToolbarManager().setProgressBarEnabled(false); 204 205 DisplayAndroid primaryDisplay = DisplayAndroid.getNonMultiDisplay(activity); 206 mContentVirtualDisplay = VirtualDisplayAndroid.createVirtualDisplay(); 207 mContentVirtualDisplay.setTo(primaryDisplay); 208 209 mContentVrWindowAndroid = new VrWindowAndroid(mActivity, mContentVirtualDisplay); 210 reparentAllTabs(mContentVrWindowAndroid); 211 212 mCompositorView = mActivity.getCompositorViewHolder().getCompositorView(); 213 mVrCompositorSurfaceManager = new VrCompositorSurfaceManager(mCompositorView); 214 mCompositorView.replaceSurfaceManagerForVr( 215 mVrCompositorSurfaceManager, mContentVrWindowAndroid); 216 217 if (mVrBrowsingEnabled) { 218 injectVrRootView(); 219 } 220 221 // This overrides the default intent created by GVR to return to Chrome when the DON flow 222 // is triggered by resuming the GvrLayout, which is the usual way Daydream apps enter VR. 223 // See VrShellDelegate#getEnterVrPendingIntent for why we need to do this. 224 setReentryIntent(VrShellDelegate.getEnterVrPendingIntent(activity)); 225 226 setPresentationView(mPresentationView); 227 228 getUiLayout().setCloseButtonListener(mDelegate.getVrCloseButtonListener()); 229 getUiLayout().setSettingsButtonListener(mDelegate.getVrSettingsButtonListener()); 230 231 if (mVrBrowsingEnabled) injectVrHostedUiView(); 232 233 // This has to happen after VrModalDialogManager is created. 234 mNonVrUiWidgetFactory = UiWidgetFactory.getInstance(); 235 UiWidgetFactory.setInstance(new VrUiWidgetFactory(this, mActivity.getModalDialogManager())); 236 237 mRedirectHandler = new RedirectHandler() { 238 @Override 239 public boolean shouldStayInApp(boolean hasExternalProtocol) { 240 return !hasExternalProtocol; 241 } 242 }; 243 244 mTabObserver = new EmptyTabObserver() { 245 @Override 246 public void onContentChanged(Tab tab) { 247 // Restore proper focus on the old content. 248 if (mViewEventSink != null) mViewEventSink.onWindowFocusChanged(true); 249 mViewEventSink = null; 250 if (mNativeVrShell == 0) return; 251 if (mLastContentWidth != 0) { 252 setContentCssSize(mLastContentWidth, mLastContentHeight, mLastContentDpr); 253 } 254 if (tab != null && tab.getContentView() != null && tab.getWebContents() != null) { 255 tab.getContentView().requestFocus(); 256 // We need the content layer to think it has Window Focus so it doesn't blur 257 // the page, even though we're drawing VR layouts over top of it. 258 mViewEventSink = ViewEventSink.from(tab.getWebContents()); 259 if (mViewEventSink != null) mViewEventSink.onWindowFocusChanged(true); 260 } 261 VrShellJni.get().swapContents(mNativeVrShell, VrShell.this, tab); 262 updateHistoryButtonsVisibility(); 263 } 264 265 @Override 266 public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) { 267 onContentChanged(tab); 268 // It is not needed to restore IME for old web contents as it is going away and 269 // replaced by the new web contents. 270 configWebContentsImeForVr(tab.getWebContents()); 271 } 272 273 @Override 274 public void onLoadProgressChanged(Tab tab, float progress) { 275 if (mNativeVrShell == 0) return; 276 VrShellJni.get().onLoadProgressChanged(mNativeVrShell, VrShell.this, progress); 277 } 278 279 @Override 280 public void onCrash(Tab tab) { 281 updateHistoryButtonsVisibility(); 282 } 283 284 @Override 285 public void onLoadStarted(Tab tab, boolean toDifferentDocument) { 286 if (!toDifferentDocument) return; 287 updateHistoryButtonsVisibility(); 288 } 289 290 @Override 291 public void onLoadStopped(Tab tab, boolean toDifferentDocument) { 292 if (!toDifferentDocument) return; 293 updateHistoryButtonsVisibility(); 294 } 295 296 @Override 297 public void onUrlUpdated(Tab tab) { 298 updateHistoryButtonsVisibility(); 299 } 300 }; 301 302 mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() { 303 @Override 304 public void onChange() { 305 swapToForegroundTab(); 306 } 307 308 @Override 309 public void onNewTabCreated(Tab tab, @TabCreationState int creationState) { 310 if (mNativeVrShell == 0) return; 311 VrShellJni.get().onTabUpdated(mNativeVrShell, VrShell.this, tab.isIncognito(), 312 tab.getId(), tab.getTitle()); 313 } 314 }; 315 316 mTouchListener = new View.OnTouchListener() { 317 @Override 318 @SuppressLint("ClickableViewAccessibility") 319 public boolean onTouch(View v, MotionEvent event) { 320 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 321 VrShellJni.get().onTriggerEvent(mNativeVrShell, VrShell.this, true); 322 return true; 323 } else if (event.getActionMasked() == MotionEvent.ACTION_UP 324 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 325 VrShellJni.get().onTriggerEvent(mNativeVrShell, VrShell.this, false); 326 return true; 327 } 328 return false; 329 } 330 }; 331 } 332 injectVrRootView()333 private void injectVrRootView() { 334 // Inject a view into the hierarchy above R.id.content so that the rest of Chrome can 335 // remain unaware/uncaring of its existence. This view is used to draw the view hierarchy 336 // into a texture when browsing in VR. See https://crbug.com/793430. 337 View content = mActivity.getWindow().findViewById(android.R.id.content); 338 ViewGroup parent = (ViewGroup) content.getParent(); 339 mNonVrViews = new EmptySniffingVrViewContainer(mActivity, this); 340 parent.removeView(content); 341 parent.addView(mNonVrViews); 342 // Some views in Clank are just added next to the content view, like the 2D tab switcher. 343 // We need to create a parent to contain the content view and all of its siblings so that 344 // the VrViewContainer can inject input into the parent and not care about how to do its own 345 // input targeting. 346 FrameLayout childHolder = new FrameLayout(mActivity); 347 mNonVrViews.addView(childHolder); 348 childHolder.addView(content); 349 } 350 injectVrHostedUiView()351 private void injectVrHostedUiView() { 352 mNonVrModalDialogManager = mActivity.getModalDialogManager(); 353 mNonVrModalDialogManager.dismissAllDialogs(DialogDismissalCause.UNKNOWN); 354 mVrModalPresenter = new VrModalPresenter(mActivity, this); 355 mVrModalDialogManager = 356 new ModalDialogManager(mVrModalPresenter, ModalDialogManager.ModalDialogType.APP); 357 setModalDialogManager(mVrModalDialogManager); 358 359 ViewGroup decor = (ViewGroup) mActivity.getWindow().getDecorView(); 360 mUiView = new FrameLayout(decor.getContext()); 361 LayoutParams params = new FrameLayout.LayoutParams( 362 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 363 decor.addView(mUiView, params); 364 mVrUiViewContainer = new VrViewContainer(mActivity); 365 mUiView.addView(mVrUiViewContainer); 366 } 367 setModalDialogManager(ModalDialogManager modalDialogManager)368 private void setModalDialogManager(ModalDialogManager modalDialogManager) { 369 ((ObservableSupplierImpl) mActivity.getModalDialogManagerSupplier()) 370 .set(mVrModalDialogManager); 371 } 372 removeVrRootView()373 private void removeVrRootView() { 374 ViewGroup contentViewParent = (ViewGroup) mNonVrViews.getParent(); 375 assert mNonVrViews.getChildCount() == 1; 376 ViewGroup childHolder = (ViewGroup) mNonVrViews.getChildAt(0); 377 mNonVrViews.removeAllViews(); 378 contentViewParent.removeView(mNonVrViews); 379 int children = childHolder.getChildCount(); 380 assert children >= 1; 381 for (int i = 0; i < children; ++i) { 382 View child = childHolder.getChildAt(0); 383 childHolder.removeView(child); 384 contentViewParent.addView(child); 385 } 386 // Ensure the omnibox doesn't get initial focus (as it would when re-attaching the views 387 // to a window), and immediately bring up the keyboard. 388 if (mActivity.getCompositorViewHolder() != null) { 389 mActivity.getCompositorViewHolder().requestFocus(); 390 } 391 } 392 393 @TargetApi(Build.VERSION_CODES.N) initializeNative(boolean forWebVr, boolean isStandaloneVrDevice)394 public void initializeNative(boolean forWebVr, boolean isStandaloneVrDevice) { 395 Tab tab = mActivity.getActivityTab(); 396 if (mActivity.isInOverviewMode() || tab == null) { 397 openNewTab(false /*incognito*/); 398 tab = mActivity.getActivityTab(); 399 } 400 401 // Start with content rendering paused if the renderer-drawn controls are visible, as this 402 // would cause the in-content omnibox to be shown to users. 403 boolean pauseContent = mActivity.getBrowserControlsManager().getContentOffset() > 0; 404 405 // Get physical and pixel size of the display, which is needed by native 406 // to dynamically calculate the content's resolution and window size. 407 DisplayMetrics dm = new DisplayMetrics(); 408 DisplayAndroidManager.getDefaultDisplayForContext(mActivity).getRealMetrics(dm); 409 // We're supposed to be in landscape at this point, but it's possible for us to get here 410 // before the change has fully propagated. In this case, the width and height are swapped, 411 // which causes an incorrect display size to be used, and the page to appear zoomed in. 412 if (dm.widthPixels < dm.heightPixels) { 413 int tempWidth = dm.heightPixels; 414 dm.heightPixels = dm.widthPixels; 415 dm.widthPixels = tempWidth; 416 float tempXDpi = dm.ydpi; 417 dm.xdpi = dm.ydpi; 418 dm.ydpi = tempXDpi; 419 // In the case where we're still in portrait, keep the black overlay visible until the 420 // GvrLayout is in the correct orientation. 421 } else { 422 VrModuleProvider.getDelegate().removeBlackOverlayView(mActivity, false /* animate */); 423 } 424 float displayWidthMeters = (dm.widthPixels / dm.xdpi) * INCHES_TO_METERS; 425 float displayHeightMeters = (dm.heightPixels / dm.ydpi) * INCHES_TO_METERS; 426 427 // Semi-arbitrary resolution cutoff that determines how much we scale our default buffer 428 // size in VR. This is so we can make the right performance/quality tradeoff for both the 429 // relatively low-res Pixel, and higher-res Pixel XL and other devices. 430 boolean lowDensity = dm.densityDpi <= DisplayMetrics.DENSITY_XXHIGH; 431 432 boolean hasOrCanRequestRecordAudioPermission = 433 hasRecordAudioPermission() || canRequestRecordAudioPermission(); 434 boolean supportsRecognition = VoiceRecognitionUtil.isRecognitionIntentPresent(false); 435 mNativeVrShell = VrShellJni.get().init(VrShell.this, mDelegate, forWebVr, 436 !mVrBrowsingEnabled, hasOrCanRequestRecordAudioPermission && supportsRecognition, 437 getGvrApi().getNativeGvrContext(), mReprojectedRendering, displayWidthMeters, 438 displayHeightMeters, dm.widthPixels, dm.heightPixels, pauseContent, lowDensity, 439 isStandaloneVrDevice); 440 441 swapToTab(tab); 442 createTabList(); 443 mActivity.getTabModelSelector().addObserver(mTabModelSelectorObserver); 444 attachTabModelSelectorTabObserver(); 445 updateHistoryButtonsVisibility(); 446 447 mPresentationView.setOnTouchListener(mTouchListener); 448 449 if (mVrBrowsingEnabled) { 450 mAndroidUiGestureTarget = new AndroidUiGestureTarget(mNonVrViews.getInputTarget(), 451 mContentVrWindowAndroid.getDisplay().getDipScale(), getNativePageScrollRatio(), 452 getTouchSlop()); 453 VrShellJni.get().setAndroidGestureTarget( 454 mNativeVrShell, VrShell.this, mAndroidUiGestureTarget); 455 } 456 } 457 createTabList()458 private void createTabList() { 459 assert mNativeVrShell != 0; 460 TabModel main = mTabModelSelector.getModel(false); 461 int count = main.getCount(); 462 Tab[] mainTabs = new Tab[count]; 463 for (int i = 0; i < count; ++i) { 464 mainTabs[i] = main.getTabAt(i); 465 } 466 TabModel incognito = mTabModelSelector.getModel(true); 467 count = incognito.getCount(); 468 Tab[] incognitoTabs = new Tab[count]; 469 for (int i = 0; i < count; ++i) { 470 incognitoTabs[i] = incognito.getTabAt(i); 471 } 472 VrShellJni.get().onTabListCreated(mNativeVrShell, VrShell.this, mainTabs, incognitoTabs); 473 } 474 swapToForegroundTab()475 private void swapToForegroundTab() { 476 Tab tab = mActivity.getActivityTab(); 477 if (tab == mTab) return; 478 swapToTab(tab); 479 } 480 swapToTab(Tab tab)481 private void swapToTab(Tab tab) { 482 if (mTab != null) { 483 mTab.removeObserver(mTabObserver); 484 restoreTabFromVR(); 485 } 486 487 mTab = tab; 488 if (mTab != null) { 489 initializeTabForVR(); 490 mTab.addObserver(mTabObserver); 491 TabBrowserControlsConstraintsHelper.update(mTab, BrowserControlsState.HIDDEN, false); 492 } 493 mTabObserver.onContentChanged(mTab); 494 } 495 configWebContentsImeForVr(WebContents webContents)496 private void configWebContentsImeForVr(WebContents webContents) { 497 if (webContents == null) return; 498 499 ImeAdapter imeAdapter = ImeAdapter.fromWebContents(webContents); 500 if (imeAdapter == null) return; 501 502 mInputMethodManagerWrapper = new VrInputMethodManagerWrapper(mActivity, this); 503 imeAdapter.setInputMethodManagerWrapper(mInputMethodManagerWrapper); 504 } 505 restoreWebContentsImeFromVr(WebContents webContents)506 private void restoreWebContentsImeFromVr(WebContents webContents) { 507 if (webContents == null) return; 508 509 ImeAdapter imeAdapter = ImeAdapter.fromWebContents(webContents); 510 if (imeAdapter == null) return; 511 512 // Use application context here to avoid leaking the activity context. 513 imeAdapter.setInputMethodManagerWrapper(ImeAdapter.createDefaultInputMethodManagerWrapper( 514 mActivity.getApplicationContext(), mContentVrWindowAndroid, null)); 515 mInputMethodManagerWrapper = null; 516 } 517 initializeTabForVR()518 private void initializeTabForVR() { 519 if (mTab == null) return; 520 // Make sure we are not redirecting to another app, i.e. out of VR mode. 521 mNonVrRedirectHandler = RedirectHandlerTabHelper.swapHandlerFor(mTab, mRedirectHandler); 522 assert mTab.getWindowAndroid() == mContentVrWindowAndroid; 523 configWebContentsImeForVr(mTab.getWebContents()); 524 } 525 restoreTabFromVR()526 private void restoreTabFromVR() { 527 if (mTab == null) return; 528 RedirectHandlerTabHelper.swapHandlerFor(mTab, mNonVrRedirectHandler); 529 mNonVrRedirectHandler = null; 530 restoreWebContentsImeFromVr(mTab.getWebContents()); 531 } 532 reparentAllTabs(WindowAndroid window)533 private void reparentAllTabs(WindowAndroid window) { 534 // Ensure new tabs are created with the correct window. 535 boolean[] values = {true, false}; 536 for (boolean incognito : values) { 537 TabCreator tabCreator = mActivity.getTabCreator(incognito); 538 if (tabCreator instanceof ChromeTabCreator) { 539 ((ChromeTabCreator) tabCreator).setWindowAndroid(window); 540 } 541 } 542 543 // Reparent all existing tabs. 544 for (TabModel model : mActivity.getTabModelSelector().getModels()) { 545 for (int i = 0; i < model.getCount(); ++i) { 546 model.getTabAt(i).updateAttachment(window, null); 547 } 548 } 549 } 550 551 // Returns true if Chrome has permission to use audio input. 552 @CalledByNative hasRecordAudioPermission()553 public boolean hasRecordAudioPermission() { 554 return mDelegate.hasRecordAudioPermission(); 555 } 556 557 // Returns true if Chrome has not been permanently denied audio input permission. 558 @CalledByNative canRequestRecordAudioPermission()559 public boolean canRequestRecordAudioPermission() { 560 return mDelegate.canRequestRecordAudioPermission(); 561 } 562 563 // Exits VR, telling the user to remove their headset, and returning to Chromium. 564 @CalledByNative forceExitVr()565 public void forceExitVr() { 566 mDelegate.showDoff(false); 567 } 568 569 // Called when the user clicks on the security icon in the URL bar. 570 @CalledByNative showPageInfo()571 public void showPageInfo() { 572 Tab tab = mActivity.getActivityTab(); 573 if (tab == null) return; 574 WebContents webContents = tab.getWebContents(); 575 PageInfoController.show(mActivity, webContents, null, 576 PageInfoController.OpenedFromSource.VR, 577 new ChromePageInfoControllerDelegate(mActivity, webContents, 578 mActivity::getModalDialogManager, 579 /*offlinePageLoadUrlDelegate=*/ 580 new OfflinePageUtils.TabOfflinePageLoadUrlDelegate(tab)), 581 new ChromePermissionParamsListBuilderDelegate()); 582 } 583 584 // Called because showing audio permission dialog isn't supported in VR. This happens when 585 // the user wants to do a voice search. 586 @CalledByNative onUnhandledPermissionPrompt()587 public void onUnhandledPermissionPrompt() { 588 VrShellDelegate.requestToExitVr(new OnExitVrRequestListener() { 589 @Override 590 public void onSucceeded() { 591 PermissionCallback callback = new PermissionCallback() { 592 @Override 593 public void onRequestPermissionsResult( 594 String[] permissions, int[] grantResults) { 595 PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() { 596 @Override 597 public void run() { 598 VrShellDelegate.enterVrIfNecessary(); 599 600 // In SVR, the native VR UI is destroyed when 601 // exiting VR (mNativeVrShell == 0), so 602 // permission changes will be detected when the 603 // VR UI is reconstructed. For AIO devices this 604 // doesn't happen, so we need to notify native 605 // UI of the permission change immediately. 606 if (mNativeVrShell != 0) { 607 VrShellJni.get().requestRecordAudioPermissionResult( 608 mNativeVrShell, VrShell.this, 609 grantResults[0] == PackageManager.PERMISSION_GRANTED); 610 } 611 } 612 }); 613 } 614 }; 615 String[] permissionArray = new String[1]; 616 permissionArray[0] = android.Manifest.permission.RECORD_AUDIO; 617 mActivity.getWindowAndroid().requestPermissions(permissionArray, callback); 618 } 619 620 @Override 621 public void onDenied() {} 622 }, UiUnsupportedMode.VOICE_SEARCH_NEEDS_RECORD_AUDIO_OS_PERMISSION); 623 } 624 625 // Called when the user has an older GVR Keyboard installed on their device and we need them to 626 // have a newer one. 627 @CalledByNative onNeedsKeyboardUpdate()628 public void onNeedsKeyboardUpdate() { 629 VrShellDelegate.requestToExitVr(new OnExitVrRequestListener() { 630 @Override 631 public void onSucceeded() { 632 mDelegate.promptForKeyboardUpdate(); 633 } 634 635 @Override 636 public void onDenied() {} 637 }, UiUnsupportedMode.NEEDS_KEYBOARD_UPDATE); 638 } 639 640 // Close the current hosted Dialog in VR 641 @CalledByNative closeCurrentDialog()642 public void closeCurrentDialog() { 643 mVrModalPresenter.closeCurrentDialog(); 644 if (mVrDialogDismissHandler != null) { 645 mVrDialogDismissHandler.run(); 646 mVrDialogDismissHandler = null; 647 } 648 } 649 650 /** 651 * @param vrDialogDismissHandler the mVrDialogDismissHandler to set 652 */ 653 @Override setVrDialogDismissHandler(Runnable vrDialogDismissHandler)654 public void setVrDialogDismissHandler(Runnable vrDialogDismissHandler) { 655 mVrDialogDismissHandler = vrDialogDismissHandler; 656 } 657 658 @Override onWindowFocusChanged(boolean focused)659 public void onWindowFocusChanged(boolean focused) { 660 // This handles the case where we open 2D popups in 2D-in-VR. We lose window focus, but stay 661 // resumed, so we have to listen for focus gain to know when the popup was closed. However, 662 // we pause VrShellImpl so that we don't react to input from the controller nor do any 663 // rendering. This also handles the case where we're launched via intent and turn VR mode on 664 // with a popup open. We'll lose window focus when the popup 'gets shown' and know to turn 665 // VR mode off. 666 // TODO(asimjour): Focus is a bad signal. We should listen for windows being created and 667 // destroyed if possible. 668 if (VrModuleProvider.getDelegate().bootsToVr()) { 669 if (focused) { 670 resume(); 671 } else { 672 pause(); 673 } 674 VrShellDelegate.setVrModeEnabled(mActivity, focused); 675 setVisibility(focused ? View.VISIBLE : View.INVISIBLE); 676 } 677 } 678 679 @CalledByNative setContentCssSize(float width, float height, float dpr)680 public void setContentCssSize(float width, float height, float dpr) { 681 ThreadUtils.assertOnUiThread(); 682 boolean surfaceUninitialized = mLastContentWidth == 0; 683 mLastContentWidth = width; 684 mLastContentHeight = height; 685 mLastContentDpr = dpr; 686 687 // Java views don't listen to our DPR changes, so to get them to render at the correct 688 // size we need to make them larger. 689 DisplayAndroid primaryDisplay = DisplayAndroid.getNonMultiDisplay(mActivity); 690 float dip = primaryDisplay.getDipScale(); 691 692 int contentWidth = (int) Math.ceil(width * dpr); 693 int contentHeight = (int) Math.ceil(height * dpr); 694 695 int overlayWidth = (int) Math.ceil(width * dip); 696 int overlayHeight = (int) Math.ceil(height * dip); 697 698 VrShellJni.get().bufferBoundsChanged(mNativeVrShell, VrShell.this, contentWidth, 699 contentHeight, overlayWidth, overlayHeight); 700 if (mContentSurface != null) { 701 if (surfaceUninitialized) { 702 mVrCompositorSurfaceManager.setSurface( 703 mContentSurface, PixelFormat.OPAQUE, contentWidth, contentHeight); 704 } else { 705 mVrCompositorSurfaceManager.surfaceResized(contentWidth, contentHeight); 706 } 707 } 708 Point size = new Point(contentWidth, contentHeight); 709 mContentVirtualDisplay.update( 710 size, dpr, dip / dpr, null, null, null, null, null, null, null, null); 711 if (mTab != null && mTab.getWebContents() != null) { 712 mTab.getWebContents().setSize(contentWidth, contentHeight); 713 } 714 if (mVrBrowsingEnabled) mNonVrViews.resize(overlayWidth, overlayHeight); 715 } 716 717 @CalledByNative contentSurfaceCreated(Surface surface)718 public void contentSurfaceCreated(Surface surface) { 719 mContentSurface = surface; 720 if (mLastContentWidth == 0) return; 721 int width = (int) Math.ceil(mLastContentWidth * mLastContentDpr); 722 int height = (int) Math.ceil(mLastContentHeight * mLastContentDpr); 723 mVrCompositorSurfaceManager.setSurface(mContentSurface, PixelFormat.OPAQUE, width, height); 724 } 725 726 @CalledByNative contentOverlaySurfaceCreated(Surface surface)727 public void contentOverlaySurfaceCreated(Surface surface) { 728 if (mVrBrowsingEnabled) mNonVrViews.setSurface(surface); 729 } 730 731 @CalledByNative dialogSurfaceCreated(Surface surface)732 public void dialogSurfaceCreated(Surface surface) { 733 if (mVrBrowsingEnabled && mVrUiViewContainer != null) { 734 mVrUiViewContainer.setSurface(surface); 735 } 736 } 737 738 @Override dispatchTouchEvent(MotionEvent event)739 public boolean dispatchTouchEvent(MotionEvent event) { 740 boolean parentConsumed = super.dispatchTouchEvent(event); 741 if (mOnDispatchTouchEventForTesting != null) { 742 mOnDispatchTouchEventForTesting.onDispatchTouchEvent(parentConsumed); 743 } 744 return parentConsumed; 745 } 746 747 @Override dispatchKeyEvent(KeyEvent event)748 public boolean dispatchKeyEvent(KeyEvent event) { 749 if (mTab != null && mTab.getWebContents() != null 750 && mTab.getWebContents().getEventForwarder().dispatchKeyEvent(event)) { 751 return true; 752 } 753 return super.dispatchKeyEvent(event); 754 } 755 756 @Override onGenericMotionEvent(MotionEvent event)757 public boolean onGenericMotionEvent(MotionEvent event) { 758 if (mTab != null && mTab.getWebContents() != null 759 && mTab.getWebContents().getEventForwarder().onGenericMotionEvent(event)) { 760 return true; 761 } 762 return super.onGenericMotionEvent(event); 763 } 764 765 @Override onResume()766 public void onResume() { 767 if (mPaused != null && !mPaused) return; 768 mPaused = false; 769 super.onResume(); 770 if (mNativeVrShell != 0) { 771 // Refreshing the viewer profile may accesses disk under some circumstances outside of 772 // our control. 773 try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) { 774 VrShellJni.get().onResume(mNativeVrShell, VrShell.this); 775 } 776 } 777 } 778 779 @Override onPause()780 public void onPause() { 781 if (mPaused != null && mPaused) return; 782 mPaused = true; 783 super.onPause(); 784 if (mNativeVrShell != 0) VrShellJni.get().onPause(mNativeVrShell, VrShell.this); 785 } 786 destroyWindowAndroid()787 public void destroyWindowAndroid() { 788 reparentAllTabs(mActivity.getWindowAndroid()); 789 mCompositorView.onExitVr(mActivity.getWindowAndroid()); 790 mContentVrWindowAndroid.destroy(); 791 } 792 793 @Override shutdown()794 public void shutdown() { 795 if (mVrBrowsingEnabled) { 796 if (mVrModalDialogManager != null) { 797 mVrModalDialogManager.dismissAllDialogs(DialogDismissalCause.UNKNOWN); 798 setModalDialogManager(mNonVrModalDialogManager); 799 mVrModalDialogManager = null; 800 } 801 mNonVrViews.destroy(); 802 if (mVrUiViewContainer != null) mVrUiViewContainer.destroy(); 803 removeVrRootView(); 804 } 805 806 if (!mActivity.isActivityFinishingOrDestroyed()) { 807 mActivity.getFullscreenManager().exitPersistentFullscreenMode(); 808 } 809 reparentAllTabs(mActivity.getWindowAndroid()); 810 if (mNativeVrShell != 0) { 811 VrShellJni.get().destroy(mNativeVrShell, VrShell.this); 812 mNativeVrShell = 0; 813 } 814 mTabModelSelector.removeObserver(mTabModelSelectorObserver); 815 mTabModelSelectorTabObserver.destroy(); 816 if (mTab != null) { 817 mTab.removeObserver(mTabObserver); 818 restoreTabFromVR(); 819 restoreWebContentsImeFromVr(mTab.getWebContents()); 820 if (mTab.getWebContents() != null && mTab.getContentView() != null) { 821 View parent = mTab.getContentView(); 822 mTab.getWebContents().setSize(parent.getWidth(), parent.getHeight()); 823 } 824 TabBrowserControlsConstraintsHelper.update(mTab, BrowserControlsState.SHOWN, false); 825 } 826 827 mContentVirtualDisplay.destroy(); 828 829 mCompositorView.onExitVr(mActivity.getWindowAndroid()); 830 mContentVrWindowAndroid.destroy(); 831 832 if (mActivity.getToolbarManager() != null) { 833 mActivity.getToolbarManager().setProgressBarEnabled(true); 834 } 835 836 if (mNonVrUiWidgetFactory != null) UiWidgetFactory.setInstance(mNonVrUiWidgetFactory); 837 838 FrameLayout decor = (FrameLayout) mActivity.getWindow().getDecorView(); 839 decor.removeView(mUiView); 840 super.shutdown(); 841 } 842 pause()843 public void pause() { 844 onPause(); 845 } 846 resume()847 public void resume() { 848 onResume(); 849 } 850 teardown()851 public void teardown() { 852 shutdown(); 853 } 854 hasUiFinishedLoading()855 public boolean hasUiFinishedLoading() { 856 return VrShellJni.get().hasUiFinishedLoading(mNativeVrShell, VrShell.this); 857 } 858 859 /** 860 * Set View for the Dialog that should show up on top of the main content. 861 */ 862 @Override setDialogView(View view)863 public void setDialogView(View view) { 864 if (view == null) return; 865 assert mVrUiViewContainer.getChildCount() == 0; 866 mVrUiViewContainer.addView(view); 867 } 868 869 /** 870 * Close the popup Dialog in VR. 871 */ 872 @Override closeVrDialog()873 public void closeVrDialog() { 874 VrShellJni.get().closeAlertDialog(mNativeVrShell, VrShell.this); 875 mVrUiViewContainer.removeAllViews(); 876 mVrDialogDismissHandler = null; 877 } 878 879 /** 880 * Set size of the Dialog in VR. 881 */ 882 @Override setDialogSize(int width, int height)883 public void setDialogSize(int width, int height) { 884 VrShellJni.get().setDialogBufferSize(mNativeVrShell, VrShell.this, width, height); 885 VrShellJni.get().setAlertDialogSize(mNativeVrShell, VrShell.this, width, height); 886 } 887 888 /** 889 * Set size of the Dialog location in VR. 890 */ 891 @Override setDialogLocation(int x, int y)892 public void setDialogLocation(int x, int y) { 893 if (getWebVrModeEnabled()) return; 894 float dipScale = DisplayAndroid.getNonMultiDisplay(mActivity).getDipScale(); 895 float w = mLastContentWidth * dipScale; 896 float h = mLastContentHeight * dipScale; 897 float scale = mContentVrWindowAndroid.getDisplay().getAndroidUIScaling(); 898 VrShellJni.get().setDialogLocation( 899 mNativeVrShell, VrShell.this, x * scale / w, y * scale / h); 900 } 901 902 @Override setDialogFloating(boolean floating)903 public void setDialogFloating(boolean floating) { 904 VrShellJni.get().setDialogFloating(mNativeVrShell, VrShell.this, floating); 905 } 906 907 /** 908 * Initialize the Dialog in VR. 909 */ 910 @Override initVrDialog(int width, int height)911 public void initVrDialog(int width, int height) { 912 VrShellJni.get().setAlertDialog(mNativeVrShell, VrShell.this, width, height); 913 mAndroidDialogGestureTarget = 914 new AndroidUiGestureTarget(mVrUiViewContainer.getInputTarget(), 1.0f, 915 getNativePageScrollRatio(), getTouchSlop()); 916 VrShellJni.get().setDialogGestureTarget( 917 mNativeVrShell, VrShell.this, mAndroidDialogGestureTarget); 918 } 919 920 /** 921 * Show a text only Toast. 922 */ 923 @Override showToast(CharSequence text)924 public void showToast(CharSequence text) { 925 VrShellJni.get().showToast(mNativeVrShell, VrShell.this, text.toString()); 926 } 927 928 /** 929 * Cancel a Toast. 930 */ 931 @Override cancelToast()932 public void cancelToast() { 933 VrShellJni.get().cancelToast(mNativeVrShell, VrShell.this); 934 } 935 setWebVrModeEnabled(boolean enabled)936 public void setWebVrModeEnabled(boolean enabled) { 937 if (mNativeVrShell != 0) { 938 VrShellJni.get().setWebVrMode(mNativeVrShell, VrShell.this, enabled); 939 } 940 if (!enabled) { 941 mContentVrWindowAndroid.setVSyncPaused(false); 942 mPendingVSyncPause = false; 943 return; 944 } 945 // Wait for the compositor to produce a frame to allow the omnibox to start hiding 946 // before we pause VSync. Control heights may not be correct as the omnibox might 947 // animate, but this is handled when exiting VR. 948 mPendingVSyncPause = true; 949 mActivity.getCompositorViewHolder().getCompositorView().surfaceRedrawNeededAsync(() -> { 950 if (mPendingVSyncPause) { 951 mContentVrWindowAndroid.setVSyncPaused(true); 952 mPendingVSyncPause = false; 953 if (mOnVSyncPausedForTesting != null) { 954 mOnVSyncPausedForTesting.run(); 955 } 956 } 957 }); 958 } 959 getWebVrModeEnabled()960 public boolean getWebVrModeEnabled() { 961 if (mNativeVrShell == 0) return false; 962 return VrShellJni.get().getWebVrMode(mNativeVrShell, VrShell.this); 963 } 964 isDisplayingUrlForTesting()965 public boolean isDisplayingUrlForTesting() { 966 assert mNativeVrShell != 0; 967 return PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> { 968 return VrShellJni.get().isDisplayingUrlForTesting(mNativeVrShell, VrShell.this); 969 }); 970 } 971 972 @VisibleForTesting getVrInputConnectionForTesting()973 public VrInputConnection getVrInputConnectionForTesting() { 974 assert mNativeVrShell != 0; 975 return VrShellJni.get().getVrInputConnectionForTesting(mNativeVrShell, VrShell.this); 976 } 977 getContainer()978 public FrameLayout getContainer() { 979 return this; 980 } 981 rawTopContentOffsetChanged(float topContentOffset)982 public void rawTopContentOffsetChanged(float topContentOffset) { 983 if (topContentOffset != 0) return; 984 // Wait until a new frame is definitely available. 985 mActivity.getCompositorViewHolder().getCompositorView().surfaceRedrawNeededAsync(() -> { 986 if (mNativeVrShell != 0) { 987 VrShellJni.get().resumeContentRendering(mNativeVrShell, VrShell.this); 988 } 989 }); 990 } 991 992 @Override surfaceCreated(SurfaceHolder holder)993 public void surfaceCreated(SurfaceHolder holder) { 994 if (mNativeVrShell == 0) return; 995 VrShellJni.get().setSurface(mNativeVrShell, VrShell.this, holder.getSurface()); 996 } 997 998 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)999 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 1000 // No need to do anything here, we don't care about surface width/height. 1001 } 1002 1003 @Override surfaceDestroyed(SurfaceHolder holder)1004 public void surfaceDestroyed(SurfaceHolder holder) { 1005 mVrCompositorSurfaceManager.surfaceDestroyed(); 1006 VrShellDelegate.forceExitVrImmediately(); 1007 } 1008 1009 /** Creates and attaches a TabModelSelectorTabObserver to the tab model selector. */ attachTabModelSelectorTabObserver()1010 private void attachTabModelSelectorTabObserver() { 1011 assert mTabModelSelectorTabObserver == null; 1012 mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(mTabModelSelector) { 1013 @Override 1014 public void onTitleUpdated(Tab tab) { 1015 if (mNativeVrShell == 0) return; 1016 VrShellJni.get().onTabUpdated(mNativeVrShell, VrShell.this, tab.isIncognito(), 1017 tab.getId(), tab.getTitle()); 1018 } 1019 1020 @Override 1021 public void onClosingStateChanged(Tab tab, boolean closing) { 1022 if (mNativeVrShell == 0) return; 1023 if (closing) { 1024 VrShellJni.get().onTabRemoved( 1025 mNativeVrShell, VrShell.this, tab.isIncognito(), tab.getId()); 1026 } else { 1027 VrShellJni.get().onTabUpdated(mNativeVrShell, VrShell.this, tab.isIncognito(), 1028 tab.getId(), tab.getTitle()); 1029 } 1030 } 1031 1032 @Override 1033 public void onDestroyed(Tab tab) { 1034 if (mNativeVrShell == 0) return; 1035 VrShellJni.get().onTabRemoved( 1036 mNativeVrShell, VrShell.this, tab.isIncognito(), tab.getId()); 1037 } 1038 }; 1039 } 1040 1041 @CalledByNative hasDaydreamSupport()1042 public boolean hasDaydreamSupport() { 1043 return VrCoreInstallUtils.hasDaydreamSupport(); 1044 } 1045 requestToExitVr(@iUnsupportedMode int reason, boolean showExitPromptBeforeDoff)1046 public void requestToExitVr(@UiUnsupportedMode int reason, boolean showExitPromptBeforeDoff) { 1047 if (mNativeVrShell == 0) return; 1048 if (showExitPromptBeforeDoff) { 1049 VrShellJni.get().requestToExitVr(mNativeVrShell, VrShell.this, reason); 1050 } else { 1051 mDelegate.onExitVrRequestResult(true); 1052 } 1053 } 1054 1055 @CalledByNative onExitVrRequestResult(boolean shouldExit)1056 private void onExitVrRequestResult(boolean shouldExit) { 1057 mDelegate.onExitVrRequestResult(shouldExit); 1058 } 1059 1060 @CalledByNative loadUrl(String url)1061 private void loadUrl(String url) { 1062 if (mTab == null) { 1063 mActivity.getCurrentTabCreator().createNewTab( 1064 new LoadUrlParams(url), TabLaunchType.FROM_CHROME_UI, null); 1065 } else { 1066 mTab.loadUrl(new LoadUrlParams(url)); 1067 } 1068 } 1069 1070 @VisibleForTesting 1071 @CalledByNative navigateForward()1072 public void navigateForward() { 1073 if (!mCanGoForward) return; 1074 mActivity.getToolbarManager().forward(); 1075 updateHistoryButtonsVisibility(); 1076 } 1077 1078 @VisibleForTesting 1079 @CalledByNative navigateBack()1080 public void navigateBack() { 1081 if (!mCanGoBack) return; 1082 if (mActivity instanceof ChromeTabbedActivity) { 1083 // TODO(mthiesse): We should do this for custom tabs as well, as back for custom tabs 1084 // is also expected to close tabs. 1085 ((ChromeTabbedActivity) mActivity).handleBackPressed(); 1086 } else { 1087 mActivity.getToolbarManager().back(); 1088 } 1089 updateHistoryButtonsVisibility(); 1090 } 1091 1092 @CalledByNative reloadTab()1093 public void reloadTab() { 1094 mTab.reload(); 1095 } 1096 1097 @CalledByNative openNewTab(boolean incognito)1098 public void openNewTab(boolean incognito) { 1099 mActivity.getTabCreator(incognito).launchNTP(); 1100 } 1101 1102 @CalledByNative openBookmarks()1103 public void openBookmarks() { 1104 mActivity.onMenuOrKeyboardAction(R.id.all_bookmarks_menu_id, true); 1105 } 1106 1107 @CalledByNative openRecentTabs()1108 public void openRecentTabs() { 1109 mActivity.onMenuOrKeyboardAction(R.id.recent_tabs_menu_id, true); 1110 } 1111 1112 @CalledByNative openHistory()1113 public void openHistory() { 1114 mActivity.onMenuOrKeyboardAction(R.id.open_history_menu_id, true); 1115 } 1116 1117 @CalledByNative openDownloads()1118 public void openDownloads() { 1119 mActivity.onMenuOrKeyboardAction(R.id.downloads_menu_id, true); 1120 } 1121 1122 @CalledByNative openShare()1123 public void openShare() { 1124 mActivity.onMenuOrKeyboardAction(R.id.share_menu_id, true); 1125 } 1126 1127 @CalledByNative openSettings()1128 public void openSettings() { 1129 mActivity.onMenuOrKeyboardAction(R.id.preferences_id, true); 1130 } 1131 1132 @CalledByNative closeAllIncognitoTabs()1133 public void closeAllIncognitoTabs() { 1134 mTabModelSelector.getModel(true).closeAllTabs(); 1135 if (mTabModelSelector.getTotalTabCount() == 0) openNewTab(false); 1136 } 1137 1138 @CalledByNative openFeedback()1139 public void openFeedback() { 1140 mActivity.onMenuOrKeyboardAction(R.id.help_id, true); 1141 } 1142 updateHistoryButtonsVisibility()1143 private void updateHistoryButtonsVisibility() { 1144 if (mNativeVrShell == 0) return; 1145 if (mTab == null) { 1146 mCanGoBack = false; 1147 mCanGoForward = false; 1148 VrShellJni.get().setHistoryButtonsEnabled( 1149 mNativeVrShell, VrShell.this, mCanGoBack, mCanGoForward); 1150 return; 1151 } 1152 boolean willCloseTab = false; 1153 if (mActivity instanceof ChromeTabbedActivity) { 1154 // If hitting back would minimize Chrome, disable the back button. 1155 // See ChromeTabbedActivity#handleBackPressed(). 1156 willCloseTab = mActivity.backShouldCloseTab(mTab) 1157 && !TabAssociatedApp.isOpenedFromExternalApp(mTab); 1158 } 1159 boolean canGoBack = mTab.canGoBack() || willCloseTab; 1160 boolean canGoForward = mTab.canGoForward(); 1161 if ((mCanGoBack != null && canGoBack == mCanGoBack) 1162 && (mCanGoForward != null && canGoForward == mCanGoForward)) { 1163 return; 1164 } 1165 mCanGoBack = canGoBack; 1166 mCanGoForward = canGoForward; 1167 VrShellJni.get().setHistoryButtonsEnabled( 1168 mNativeVrShell, VrShell.this, mCanGoBack, mCanGoForward); 1169 } 1170 getNativePageScrollRatio()1171 private float getNativePageScrollRatio() { 1172 return mActivity.getWindowAndroid().getDisplay().getDipScale() 1173 / mContentVrWindowAndroid.getDisplay().getDipScale(); 1174 } 1175 getTouchSlop()1176 private int getTouchSlop() { 1177 ViewConfiguration vc = ViewConfiguration.get(mActivity); 1178 return vc.getScaledTouchSlop(); 1179 } 1180 1181 @Override onVrViewEmpty()1182 public void onVrViewEmpty() { 1183 if (mNativeVrShell != 0) { 1184 VrShellJni.get().onOverlayTextureEmptyChanged(mNativeVrShell, VrShell.this, true); 1185 } 1186 } 1187 1188 @Override onVrViewNonEmpty()1189 public void onVrViewNonEmpty() { 1190 if (mNativeVrShell != 0) { 1191 VrShellJni.get().onOverlayTextureEmptyChanged(mNativeVrShell, VrShell.this, false); 1192 } 1193 } 1194 1195 @Override onSizeChanged(int width, int height, int oldWidth, int oldHeight)1196 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { 1197 super.onSizeChanged(width, height, oldWidth, oldHeight); 1198 if (width > height) { 1199 VrModuleProvider.getDelegate().removeBlackOverlayView(mActivity, true /* animate */); 1200 } 1201 } 1202 1203 /** 1204 * Sets the callback that will be run when VrShellImpl's dispatchTouchEvent 1205 * is run and the parent consumed the event. 1206 * @param callback The Callback to be run. 1207 */ 1208 @VisibleForTesting setOnDispatchTouchEventForTesting(OnDispatchTouchEventCallback callback)1209 public void setOnDispatchTouchEventForTesting(OnDispatchTouchEventCallback callback) { 1210 mOnDispatchTouchEventForTesting = callback; 1211 } 1212 1213 /** 1214 * Sets that callback that will be run when VrShellImpl has issued the request to pause the 1215 * Android Window's VSyncs. 1216 * @param callback The Runnable to be run. 1217 */ 1218 @VisibleForTesting setOnVSyncPausedForTesting(Runnable callback)1219 public void setOnVSyncPausedForTesting(Runnable callback) { 1220 mOnVSyncPausedForTesting = callback; 1221 } 1222 1223 @VisibleForTesting isBackButtonEnabled()1224 public Boolean isBackButtonEnabled() { 1225 return mCanGoBack; 1226 } 1227 1228 @VisibleForTesting isForwardButtonEnabled()1229 public Boolean isForwardButtonEnabled() { 1230 return mCanGoForward; 1231 } 1232 1233 @VisibleForTesting getContentWidthForTesting()1234 public float getContentWidthForTesting() { 1235 return mLastContentWidth; 1236 } 1237 1238 @VisibleForTesting getContentHeightForTesting()1239 public float getContentHeightForTesting() { 1240 return mLastContentHeight; 1241 } 1242 1243 @VisibleForTesting getPresentationViewForTesting()1244 public View getPresentationViewForTesting() { 1245 return mPresentationView; 1246 } 1247 1248 @VisibleForTesting isDisplayingDialogView()1249 public boolean isDisplayingDialogView() { 1250 return mVrUiViewContainer.getChildCount() > 0; 1251 } 1252 1253 @VisibleForTesting getVrViewContainerForTesting()1254 public VrViewContainer getVrViewContainerForTesting() { 1255 return mVrUiViewContainer; 1256 } 1257 1258 @Override showSoftInput(boolean show)1259 public void showSoftInput(boolean show) { 1260 assert mNativeVrShell != 0; 1261 VrShellJni.get().showSoftInput(mNativeVrShell, VrShell.this, show); 1262 } 1263 1264 @Override updateIndices( int selectionStart, int selectionEnd, int compositionStart, int compositionEnd)1265 public void updateIndices( 1266 int selectionStart, int selectionEnd, int compositionStart, int compositionEnd) { 1267 assert mNativeVrShell != 0; 1268 VrShellJni.get().updateWebInputIndices(mNativeVrShell, VrShell.this, selectionStart, 1269 selectionEnd, compositionStart, compositionEnd); 1270 } 1271 1272 @VisibleForTesting getInputMethodManagerWrapperForTesting()1273 public VrInputMethodManagerWrapper getInputMethodManagerWrapperForTesting() { 1274 return mInputMethodManagerWrapper; 1275 } 1276 acceptDoffPromptForTesting()1277 public void acceptDoffPromptForTesting() { 1278 VrShellJni.get().acceptDoffPromptForTesting(mNativeVrShell, VrShell.this); 1279 } 1280 performControllerActionForTesting( int elementName, int actionType, PointF position)1281 public void performControllerActionForTesting( 1282 int elementName, int actionType, PointF position) { 1283 VrShellJni.get().performControllerActionForTesting( 1284 mNativeVrShell, VrShell.this, elementName, actionType, position.x, position.y); 1285 } 1286 performKeyboardInputForTesting(int inputType, String inputString)1287 public void performKeyboardInputForTesting(int inputType, String inputString) { 1288 PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> { 1289 VrShellJni.get().performKeyboardInputForTesting( 1290 mNativeVrShell, VrShell.this, inputType, inputString); 1291 }); 1292 } 1293 registerUiOperationCallbackForTesting(UiOperationData operationData)1294 public void registerUiOperationCallbackForTesting(UiOperationData operationData) { 1295 int actionType = operationData.actionType; 1296 assert actionType < UiTestOperationType.NUM_UI_TEST_OPERATION_TYPES; 1297 // Fill the ArrayLists if this is the first time the method has been called. 1298 if (mUiOperationResults == null) { 1299 mUiOperationResults = 1300 new ArrayList<Integer>(UiTestOperationType.NUM_UI_TEST_OPERATION_TYPES); 1301 mUiOperationResultCallbacks = 1302 new ArrayList<Runnable>(UiTestOperationType.NUM_UI_TEST_OPERATION_TYPES); 1303 for (int i = 0; i < UiTestOperationType.NUM_UI_TEST_OPERATION_TYPES; i++) { 1304 mUiOperationResults.add(null); 1305 mUiOperationResultCallbacks.add(null); 1306 } 1307 } 1308 mUiOperationResults.set(actionType, UiTestOperationResult.UNREPORTED); 1309 mUiOperationResultCallbacks.set(actionType, operationData.resultCallback); 1310 1311 // In the case of the UI activity quiescence callback type, we need to let the native UI 1312 // know how long to wait before timing out. 1313 if (actionType == UiTestOperationType.UI_ACTIVITY_RESULT) { 1314 VrShellJni.get().setUiExpectingActivityForTesting( 1315 mNativeVrShell, VrShell.this, operationData.timeoutMs); 1316 } else if (actionType == UiTestOperationType.ELEMENT_VISIBILITY_STATUS) { 1317 VrShellJni.get().watchElementForVisibilityStatusForTesting(mNativeVrShell, VrShell.this, 1318 operationData.elementName, operationData.timeoutMs, operationData.visibility); 1319 } 1320 } 1321 1322 public void saveNextFrameBufferToDiskForTesting(String filepathBase) { 1323 VrShellJni.get().saveNextFrameBufferToDiskForTesting( 1324 mNativeVrShell, VrShell.this, filepathBase); 1325 } 1326 1327 public int getLastUiOperationResultForTesting(int actionType) { 1328 return mUiOperationResults.get(actionType).intValue(); 1329 } 1330 1331 @CalledByNative 1332 public void reportUiOperationResultForTesting(int actionType, int result) { 1333 mUiOperationResults.set(actionType, result); 1334 mUiOperationResultCallbacks.get(actionType).run(); 1335 mUiOperationResultCallbacks.set(actionType, null); 1336 } 1337 1338 @NativeMethods 1339 interface Natives { 1340 long init(VrShell caller, VrShellDelegate delegate, boolean forWebVR, 1341 boolean browsingDisabled, boolean hasOrCanRequestRecordAudioPermission, long gvrApi, 1342 boolean reprojectedRendering, float displayWidthMeters, float displayHeightMeters, 1343 int displayWidthPixels, int displayHeightPixels, boolean pauseContent, 1344 boolean lowDensity, boolean isStandaloneVrDevice); 1345 boolean hasUiFinishedLoading(long nativeVrShell, VrShell caller); 1346 void setSurface(long nativeVrShell, VrShell caller, Surface surface); 1347 void swapContents(long nativeVrShell, VrShell caller, Tab tab); 1348 void setAndroidGestureTarget( 1349 long nativeVrShell, VrShell caller, AndroidUiGestureTarget androidUiGestureTarget); 1350 void setDialogGestureTarget( 1351 long nativeVrShell, VrShell caller, AndroidUiGestureTarget dialogGestureTarget); 1352 void destroy(long nativeVrShell, VrShell caller); 1353 void onTriggerEvent(long nativeVrShell, VrShell caller, boolean touched); 1354 void onPause(long nativeVrShell, VrShell caller); 1355 void onResume(long nativeVrShell, VrShell caller); 1356 void onLoadProgressChanged(long nativeVrShell, VrShell caller, double progress); 1357 void bufferBoundsChanged(long nativeVrShell, VrShell caller, int contentWidth, 1358 int contentHeight, int overlayWidth, int overlayHeight); 1359 void setWebVrMode(long nativeVrShell, VrShell caller, boolean enabled); 1360 boolean getWebVrMode(long nativeVrShell, VrShell caller); 1361 boolean isDisplayingUrlForTesting(long nativeVrShell, VrShell caller); 1362 void onTabListCreated( 1363 long nativeVrShell, VrShell caller, Tab[] mainTabs, Tab[] incognitoTabs); 1364 void onTabUpdated( 1365 long nativeVrShell, VrShell caller, boolean incognito, int id, String title); 1366 void onTabRemoved(long nativeVrShell, VrShell caller, boolean incognito, int id); 1367 void closeAlertDialog(long nativeVrShell, VrShell caller); 1368 void setAlertDialog(long nativeVrShell, VrShell caller, float width, float height); 1369 void setDialogBufferSize(long nativeVrShell, VrShell caller, int width, int height); 1370 void setAlertDialogSize(long nativeVrShell, VrShell caller, float width, float height); 1371 void setDialogLocation(long nativeVrShell, VrShell caller, float x, float y); 1372 void setDialogFloating(long nativeVrShell, VrShell caller, boolean floating); 1373 void showToast(long nativeVrShell, VrShell caller, String text); 1374 void cancelToast(long nativeVrShell, VrShell caller); 1375 void setHistoryButtonsEnabled( 1376 long nativeVrShell, VrShell caller, boolean canGoBack, boolean canGoForward); 1377 void requestToExitVr(long nativeVrShell, VrShell caller, @UiUnsupportedMode int reason); 1378 void showSoftInput(long nativeVrShell, VrShell caller, boolean show); 1379 void updateWebInputIndices(long nativeVrShell, VrShell caller, int selectionStart, 1380 int selectionEnd, int compositionStart, int compositionEnd); 1381 VrInputConnection getVrInputConnectionForTesting(long nativeVrShell, VrShell caller); 1382 void acceptDoffPromptForTesting(long nativeVrShell, VrShell caller); 1383 void performControllerActionForTesting(long nativeVrShell, VrShell caller, int elementName, 1384 int actionType, float x, float y); 1385 void performKeyboardInputForTesting( 1386 long nativeVrShell, VrShell caller, int inputType, String inputString); 1387 void setUiExpectingActivityForTesting( 1388 long nativeVrShell, VrShell caller, int quiescenceTimeoutMs); 1389 void saveNextFrameBufferToDiskForTesting( 1390 long nativeVrShell, VrShell caller, String filepathBase); 1391 void watchElementForVisibilityStatusForTesting(long nativeVrShell, VrShell caller, 1392 int elementName, int timeoutMs, boolean visibility); 1393 void resumeContentRendering(long nativeVrShell, VrShell caller); 1394 void onOverlayTextureEmptyChanged(long nativeVrShell, VrShell caller, boolean empty); 1395 void requestRecordAudioPermissionResult( 1396 long nativeVrShell, VrShell caller, boolean canRecordAudio); 1397 } 1398 } 1399