Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(301)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java

Issue 2776243007: Enable WebVR presentation from Chrome Custom Tab (Closed)
Patch Set: Add bug Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
index af104f87ea807d8484f72366883393b6cf7b2f52..52f6c35950b9cafcc9ba77dbbb9f7934b696577c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
@@ -12,6 +12,7 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -27,6 +28,7 @@ import android.widget.FrameLayout;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
@@ -36,16 +38,23 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Manages interactions with the VR Shell.
@@ -69,6 +78,10 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
public static final int VR_CARDBOARD = 1;
public static final int VR_DAYDREAM = 2; // Supports both Cardboard and Daydream viewer.
+ private static final String BASE_VR_FOLDER = "vr";
+ public static final String TID_FILE = "tid";
+ public static final String RESULT_SUCCESS_FILE = "success";
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({VR_NOT_AVAILABLE, VR_CARDBOARD, VR_DAYDREAM})
public @interface VrSupportLevel {}
@@ -79,13 +92,17 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private static final String CARDBOARD_CATEGORY = "com.google.intent.category.CARDBOARD";
private static final String VR_ACTIVITY_ALIAS =
- "org.chromium.chrome.browser.VRChromeTabbedActivity";
+ "org.chromium.chrome.browser.vr_shell.VrProxyActivity";
private static final long REENTER_VR_TIMEOUT_MS = 1000;
+ private static final String VR_CORE_MARKET_URI =
+ "market://details?id=" + VrCoreVersionChecker.VR_CORE_PACKAGE_ID;
+
private static VrShellDelegate sInstance;
+ private static AsyncTask<Void, Void, Void> sRegisterIntentTask;
- private final ChromeActivity mActivity;
+ private ChromeActivity mActivity;
@VrSupportLevel
private int mVrSupportLevel;
@@ -98,6 +115,7 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private TabModelSelector mTabModelSelector;
private boolean mInVr;
+ private boolean mTriedDONFlow;
private boolean mEnteringVr;
private boolean mPaused;
private int mRestoreSystemUiVisibilityFlag = -1;
@@ -108,6 +126,10 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private boolean mListeningForWebVrActivate;
private boolean mListeningForWebVrActivateBeforePause;
+ public static File getOrCreateVRDirectory() {
+ return ContextUtils.getApplicationContext().getDir(BASE_VR_FOLDER, Context.MODE_PRIVATE);
+ }
+
/**
* Called when the native library is first available.
*/
@@ -154,17 +176,6 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
}
/**
- * Handles a VR intent, entering VR in the process.
- */
- public static void enterVRFromIntent(Intent intent) {
- assert isDaydreamVrIntent(intent);
- boolean created_delegate = sInstance == null;
- VrShellDelegate instance = getInstance();
- if (instance == null) return;
- if (!instance.enterVRFromIntent() && created_delegate) instance.destroy();
- }
-
- /**
* Whether or not the intent is a Daydream VR Intent.
*/
public static boolean isDaydreamVrIntent(Intent intent) {
@@ -226,11 +237,13 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
@CalledByNative
private static VrShellDelegate getInstance() {
- Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
- if (sInstance != null && activity instanceof ChromeTabbedActivity) return sInstance;
+ return getInstance(ApplicationStatus.getLastTrackedFocusedActivity());
+ }
+
+ private static VrShellDelegate getInstance(Activity activity) {
if (!LibraryLoader.isInitialized()) return null;
- // Note that we only support ChromeTabbedActivity for now.
- if (activity == null || !(activity instanceof ChromeTabbedActivity)) return null;
+ if (activity == null || !isSupportedActivity(activity)) return null;
+ if (sInstance != null) return sInstance;
VrClassesWrapper wrapper = getVrClassesWrapper();
if (wrapper == null) return null;
sInstance = new VrShellDelegate((ChromeActivity) activity, wrapper);
@@ -238,6 +251,10 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
return sInstance;
}
+ private static boolean isSupportedActivity(Activity activity) {
+ return activity instanceof ChromeTabbedActivity || activity instanceof CustomTabActivity;
+ }
+
/**
* @return A helper class for creating VR-specific classes that may not be available at compile
* time.
@@ -266,16 +283,44 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private static PendingIntent getEnterVRPendingIntent(
VrDaydreamApi dayreamApi, Activity activity) {
- return PendingIntent.getActivity(activity, 0,
- dayreamApi.createVrIntent(new ComponentName(activity, VR_ACTIVITY_ALIAS)),
- PendingIntent.FLAG_ONE_SHOT);
+ Intent vrIntent = dayreamApi.createVrIntent(new ComponentName(activity, VR_ACTIVITY_ALIAS));
+ return PendingIntent.getActivity(activity, 0, vrIntent, PendingIntent.FLAG_ONE_SHOT);
}
/**
* Registers the Intent to fire after phone inserted into a headset.
*/
- private static void registerDaydreamIntent(VrDaydreamApi dayreamApi, Activity activity) {
- dayreamApi.registerDaydreamIntent(getEnterVRPendingIntent(dayreamApi, activity));
+ private static void registerDaydreamIntent(
+ final VrDaydreamApi dayreamApi, final Activity activity) {
+ if (sRegisterIntentTask != null) sRegisterIntentTask.cancel(true);
+ sRegisterIntentTask = new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // TODO(mthiesse, crbug.com/706845): This is a workaround for a bug in Daydream
+ // (b/33074373) where the intent we send to launchInVr is ignored after the first
+ // time we call launchInVr, and the old intent is reused. Remove TID_FILE and pass a
+ // tid in the VR Intent once our GVR deps are rolled to a version with this bug
+ // fixed.
+ File tidFile = new File(getOrCreateVRDirectory(), TID_FILE);
+ try {
+ FileOutputStream stream = new FileOutputStream(tidFile, false /* append */);
+ stream.write(Integer.toString(activity.getTaskId()).getBytes());
+ stream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file: " + tidFile.getAbsolutePath());
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void params) {
+ // TODO(mthiesse): See b/33074373. The intent passed here is only used the very
+ // first time this function is called, and seems to only be used again after device
+ // restart.
+ dayreamApi.registerDaydreamIntent(getEnterVRPendingIntent(dayreamApi, activity));
Ted C 2017/03/31 18:17:18 if it is used multiple times, should it still be O
mthiesse 2017/03/31 20:06:14 I'll take a look at this Monday, but I'm actually
+ sRegisterIntentTask = null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
@@ -312,19 +357,28 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
mNativeVrShellDelegate, frameTimeNanos, 1.0d / display.getRefreshRate());
}
});
- ApplicationStatus.registerStateListenerForActivity(this, activity);
+ ApplicationStatus.registerStateListenerForAllActivities(this);
}
@Override
public void onActivityStateChange(Activity activity, int newState) {
switch (newState) {
case ActivityState.DESTROYED:
- destroy();
+ if (activity == mActivity) destroy();
break;
case ActivityState.PAUSED:
- pauseVR();
+ if (activity == mActivity) {
+ if (sRegisterIntentTask != null) {
+ sRegisterIntentTask.cancel(true);
+ sRegisterIntentTask = null;
+ }
+ pauseVR();
+ }
break;
case ActivityState.RESUMED:
+ assert !mInVr;
+ if (!isSupportedActivity(activity)) return;
+ mActivity = (ChromeActivity) activity;
resumeVR();
break;
default:
@@ -353,41 +407,36 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
}
/**
- * Handle a VR intent, entering VR in the process unless we're unable to.
+ * Handle a successful VR DON flow, entering VR in the process unless we're unable to.
+ * @return False if VR entry failed.
*/
- private boolean enterVRFromIntent() {
- // Vr Intent is only used on Daydream devices.
- if (mVrSupportLevel != VR_DAYDREAM) return false;
+ private boolean enterVRAfterDON() {
if (mListeningForWebVrActivateBeforePause && !mRequestedWebVR) {
nativeDisplayActivate(mNativeVrShellDelegate);
- return false;
+ return true;
+ }
+ if (mInVr) {
+ setEnterVRResult(true);
+ return true;
}
+
// Normally, if the active page doesn't have a vrdisplayactivate listener, and WebVR was not
- // presenting and VrShell was not enabled, we shouldn't enter VR and Daydream Homescreen
- // should show after DON flow. But due to a failure in unregisterDaydreamIntent, we still
- // try to enterVR. Here we detect this case and force switch to Daydream Homescreen.
+ // presenting and VrShell was not enabled, the Daydream Homescreen should show after the DON
+ // flow. However, due to a failure in unregisterDaydreamIntent, we still try to enterVR, so
+ // detect this case and fail to enter VR.
if (!mListeningForWebVrActivateBeforePause && !mRequestedWebVR
&& !isVrShellEnabled(mVrSupportLevel)) {
- mVrDaydreamApi.launchVrHomescreen();
return false;
}
- if (mInVr) {
- setEnterVRResult(true);
- return false;
- }
if (!canEnterVR(mActivity.getActivityTab())) {
setEnterVRResult(false);
return false;
}
- if (mPaused) {
- // We can't enter VR before the application resumes, or we encounter bizarre crashes
- // related to gpu surfaces. Set this flag to enter VR on the next resume.
- prepareToEnterVR();
- mEnteringVr = true;
- } else {
- enterVR();
- }
+ // We can't enter VR before the application resumes, or we encounter bizarre crashes
+ // related to gpu surfaces. Set this flag to enter VR on the next resume.
+ assert !mPaused;
+ enterVR();
return true;
}
@@ -402,6 +451,7 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private void enterVR() {
if (mNativeVrShellDelegate == 0) return;
if (mInVr) return;
+ mEnteringVr = true;
if (mRestoreSystemUiVisibilityFlag == -1
|| mActivity.getResources().getConfiguration().orientation
!= Configuration.ORIENTATION_LANDSCAPE) {
@@ -513,9 +563,17 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
// due to the lack of support for unexported activities.
enterVR();
} else {
+ if (sRegisterIntentTask != null) {
+ try {
+ sRegisterIntentTask.get(500, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ return ENTER_VR_CANCELLED;
+ }
+ }
if (!mVrDaydreamApi.launchInVr(getEnterVRPendingIntent(mVrDaydreamApi, mActivity))) {
return ENTER_VR_CANCELLED;
}
+ mTriedDONFlow = true;
}
return ENTER_VR_REQUESTED;
}
@@ -540,21 +598,8 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private void resumeVR() {
mPaused = false;
- if (mVrSupportLevel == VR_NOT_AVAILABLE) return;
- if (mVrSupportLevel == VR_DAYDREAM
- && (isVrShellEnabled(mVrSupportLevel) || mListeningForWebVrActivateBeforePause)) {
- registerDaydreamIntent(mVrDaydreamApi, mActivity);
- }
-
- if (mEnteringVr) {
- enterVR();
- } else if (mRequestedWebVR) {
- // If this is still set, it means the user backed out of the DON flow, and we won't be
- // receiving an intent from daydream.
- nativeSetPresentResult(mNativeVrShellDelegate, false);
- mRequestedWebVR = false;
- }
-
+ // TODO(mthiesse): If we ever support staying in VR while paused, make sure to call resume
+ // on VrShell.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
nativeOnResume(mNativeVrShellDelegate);
@@ -562,19 +607,42 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
StrictMode.setThreadPolicy(oldPolicy);
}
- if (mInVr) {
- setupVrModeWindowFlags();
- oldPolicy = StrictMode.allowThreadDiskWrites();
+ if (mVrSupportLevel != VR_DAYDREAM) return;
+ if (mListeningForWebVrActivateBeforePause
+ || (isVrShellEnabled(mVrSupportLevel)
+ && (mActivity instanceof ChromeTabbedActivity))) {
+ registerDaydreamIntent(mVrDaydreamApi, mActivity);
+ }
+ if (!mInVr && !mTriedDONFlow && !mListeningForWebVrActivateBeforePause) return;
+
+ if (mVrDaydreamApi.isDaydreamCurrentViewer()
+ && mLastVRExit + REENTER_VR_TIMEOUT_MS > SystemClock.uptimeMillis()) {
+ enterVRInternal();
+ }
+
+ if (mTriedDONFlow || mListeningForWebVrActivateBeforePause) {
+ mTriedDONFlow = false;
+ boolean shouldEnterVr = false;
+ // We use a proxy activity to handle VR intents, which resumes this activity based on
+ // tid, so we won't get a new Intent and have to read a file to check whether the DON
+ // flow was successful.
+ oldPolicy = StrictMode.allowThreadDiskReads();
+ StrictMode.allowThreadDiskWrites();
try {
- mVrShell.resume();
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Unable to resume VrShell", e);
+ File resultFile = new File(getOrCreateVRDirectory(), RESULT_SUCCESS_FILE);
+ if (resultFile.exists()) shouldEnterVr = true;
+ resultFile.delete();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
- } else if (mVrSupportLevel == VR_DAYDREAM && mVrDaydreamApi.isDaydreamCurrentViewer()
- && mLastVRExit + REENTER_VR_TIMEOUT_MS > SystemClock.uptimeMillis()) {
- enterVRInternal();
+ // If we fail to enter VR when we should have entered VR, return to the home screen.
+ if (shouldEnterVr && !enterVRAfterDON()) mVrDaydreamApi.launchVrHomescreen();
+ }
+
+ if (mRequestedWebVR && !(mInVr || mEnteringVr)) {
+ // This means the user backed out of the DON flow, and we won't be entering VR.
+ nativeSetPresentResult(mNativeVrShellDelegate, false);
+ mRequestedWebVR = false;
}
}
@@ -612,8 +680,8 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private void onExitVRResult(boolean success) {
assert mVrSupportLevel != VR_NOT_AVAILABLE;
// For now, we don't handle re-entering VR when exit fails, so keep trying to exit.
- if (!success && sInstance.mVrDaydreamApi.exitFromVr(EXIT_VR_RESULT, new Intent())) return;
- sInstance.mVrClassesWrapper.setVrModeEnabled(sInstance.mActivity, false);
+ if (!success && mVrDaydreamApi.exitFromVr(EXIT_VR_RESULT, new Intent())) return;
+ mVrClassesWrapper.setVrModeEnabled(mActivity, false);
}
@CalledByNative
@@ -703,19 +771,18 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
return;
}
- SimpleConfirmInfoBarBuilder.create(tab,
- new SimpleConfirmInfoBarBuilder.Listener() {
- @Override
- public void onInfoBarDismissed() {}
+ SimpleConfirmInfoBarBuilder.Listener listener = new SimpleConfirmInfoBarBuilder.Listener() {
+ @Override
+ public void onInfoBarDismissed() {}
- @Override
- public boolean onInfoBarButtonClicked(boolean isPrimary) {
- activity.startActivity(new Intent(Intent.ACTION_VIEW,
- Uri.parse("market://details?id="
- + VrCoreVersionChecker.VR_CORE_PACKAGE_ID)));
- return false;
- }
- },
+ @Override
+ public boolean onInfoBarButtonClicked(boolean isPrimary) {
+ activity.startActivity(
+ new Intent(Intent.ACTION_VIEW, Uri.parse(VR_CORE_MARKET_URI)));
+ return false;
+ }
+ };
+ SimpleConfirmInfoBarBuilder.create(tab, listener,
InfoBarIdentifier.VR_SERVICES_UPGRADE_ANDROID, R.drawable.vr_services, infobarText,
buttonText, null, true);
}
@@ -800,6 +867,7 @@ public class VrShellDelegate implements ApplicationStatus.ActivityStateListener
private void destroy() {
if (sInstance == null) return;
+ shutdownVR(true, false);
if (mNativeVrShellDelegate != 0) nativeDestroy(mNativeVrShellDelegate);
ApplicationStatus.unregisterActivityStateListener(this);
sInstance = null;

Powered by Google App Engine
This is Rietveld 408576698