1 // Copyright 2020 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.app.Activity;
8 import android.content.Intent;
9 import android.net.Uri;
10 
11 import androidx.annotation.VisibleForTesting;
12 
13 import com.google.vr.ndk.base.DaydreamApi;
14 
15 import org.chromium.base.ContextUtils;
16 import org.chromium.base.Log;
17 import org.chromium.base.annotations.CalledByNative;
18 import org.chromium.base.annotations.JNINamespace;
19 import org.chromium.base.annotations.NativeMethods;
20 import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
21 import org.chromium.chrome.browser.ui.messages.infobar.SimpleConfirmInfoBarBuilder;
22 import org.chromium.content_public.browser.WebContents;
23 import org.chromium.ui.base.WindowAndroid;
24 
25 /**
26  * Manages logic around VrCore Installation and Versioning
27  */
28 @JNINamespace("vr")
29 public class VrCoreInstallUtils {
30     private static final String TAG = "VrCoreInstallUtils";
31     // Pseudo-random number to avoid request id collisions. Result codes must fit in lower 16 bits
32     // when used with startActivityForResult...
33     private static final int VR_SERVICES_UPDATE_RESULT = 7213;
34 
35     private static final String VR_CORE_MARKET_URI =
36             "market://details?id=" + VrCoreVersionChecker.VR_CORE_PACKAGE_ID;
37 
38     // Instance that requested installation of VRCore.
39     // Should be non-null only if there is a pending request to install VRCore.
40     private static VrCoreInstallUtils sRequestInstallInstance;
41 
42     private static VrCoreVersionChecker sVrCoreVersionChecker;
43     private static @VrSupportLevel Integer sVrSupportLevel;
44 
45     private long mNativeVrCoreInstallUtils;
46 
47     @CalledByNative
48     @VisibleForTesting
create(long nativeVrCoreInstallUtils)49     protected static VrCoreInstallUtils create(long nativeVrCoreInstallUtils) {
50         return new VrCoreInstallUtils(nativeVrCoreInstallUtils);
51     }
52 
53     /**
54      * See {@link Activity#onActivityResult}.
55      */
onActivityResultWithNative(int requestCode, int resultCode)56     public static boolean onActivityResultWithNative(int requestCode, int resultCode) {
57         // Handles the result of requesting to update VR services.
58         if (requestCode == VR_SERVICES_UPDATE_RESULT) {
59             if (sRequestInstallInstance != null) {
60                 sRequestInstallInstance.onVrCoreMaybeUpdated();
61                 sRequestInstallInstance = null;
62             }
63             return true;
64         }
65         return false;
66     }
67 
isDaydreamReadyDevice()68     public static boolean isDaydreamReadyDevice() {
69         return DaydreamApi.isDaydreamReadyPlatform(ContextUtils.getApplicationContext());
70     }
71 
getVrCoreVersionChecker()72     public static VrCoreVersionChecker getVrCoreVersionChecker() {
73         if (sVrCoreVersionChecker == null) sVrCoreVersionChecker = new VrCoreVersionChecker();
74         return sVrCoreVersionChecker;
75     }
76 
77     /**
78      * Returns the current {@VrSupportLevel}.
79      */
80     @CalledByNative
getVrSupportLevel()81     public static int getVrSupportLevel() {
82         if (sVrSupportLevel == null) {
83             if (!isVrCoreCompatible()) {
84                 sVrSupportLevel = VrSupportLevel.VR_NEEDS_UPDATE;
85             } else if (isDaydreamReadyDevice()) {
86                 sVrSupportLevel = VrSupportLevel.VR_DAYDREAM;
87             } else {
88                 sVrSupportLevel = VrSupportLevel.VR_CARDBOARD;
89             }
90         }
91         return sVrSupportLevel;
92     }
93 
94     /**
95      * Returns the @{VrSupportLevel}, if known, without attempting to recalculate.
96      */
getCachedVrSupportLevel()97     public static Integer getCachedVrSupportLevel() {
98         return sVrSupportLevel;
99     }
100 
101     @CalledByNative
vrSupportNeedsUpdate()102     public static boolean vrSupportNeedsUpdate() {
103         return getVrSupportLevel() == VrSupportLevel.VR_NEEDS_UPDATE;
104     }
105 
106     /**
107      * Returns whether the device has support for Daydream.
108      */
hasDaydreamSupport()109     /* package */ static boolean hasDaydreamSupport() {
110         return getVrSupportLevel() == VrSupportLevel.VR_DAYDREAM;
111     }
112 
113     /**
114      * @param versionChecker The VrCoreVersionChecker object this delegate will use
115      */
116     @VisibleForTesting
overrideVrCoreVersionChecker(VrCoreVersionChecker versionChecker)117     protected static void overrideVrCoreVersionChecker(VrCoreVersionChecker versionChecker) {
118         sVrCoreVersionChecker = versionChecker;
119         updateVrSupportLevel();
120     }
121 
isVrCoreCompatible()122     private static boolean isVrCoreCompatible() {
123         VrCoreVersionChecker checker = getVrCoreVersionChecker();
124         if (checker == null) return false;
125         return checker.getVrCoreCompatibility()
126                 == VrCoreVersionChecker.VrCoreCompatibility.VR_READY;
127     }
128 
129     /**
130      * Forces a recalculation of the current @{VrSupportLevel} as it may have changed.
131      */
updateVrSupportLevel()132     private static int updateVrSupportLevel() {
133         sVrSupportLevel = null;
134         return getVrSupportLevel();
135     }
136 
VrCoreInstallUtils(long nativeVrCoreInstallUtils)137     private VrCoreInstallUtils(long nativeVrCoreInstallUtils) {
138         mNativeVrCoreInstallUtils = nativeVrCoreInstallUtils;
139     }
140 
141     @CalledByNative
onNativeDestroy()142     private void onNativeDestroy() {
143         mNativeVrCoreInstallUtils = 0;
144     }
145 
getActivity(final WebContents webContents)146     private Activity getActivity(final WebContents webContents) {
147         if (webContents == null) return null;
148         WindowAndroid window = webContents.getTopLevelNativeWindow();
149         if (window == null) return null;
150         return window.getActivity().get();
151     }
152 
153     /**
154      * Prompts the user to install or update VRSupport if needed.
155      */
156     @CalledByNative
157     @VisibleForTesting
requestInstallVrCore(final WebContents webContents)158     protected void requestInstallVrCore(final WebContents webContents) {
159         if (webContents == null) {
160             maybeNotifyNativeOnInstallResult(false);
161             return;
162         }
163 
164         final Activity activity = getActivity(webContents);
165         if (activity == null) {
166             maybeNotifyNativeOnInstallResult(false);
167             return;
168         }
169 
170         // Force a recalculation of the VrSupportLevel
171         updateVrSupportLevel();
172         if (!vrSupportNeedsUpdate()) {
173             // If we don't need an update, just return that install succeeded.
174             maybeNotifyNativeOnInstallResult(true);
175             return;
176         }
177 
178         @VrCoreVersionChecker.VrCoreCompatibility
179         int vrCoreCompatibility = getVrCoreVersionChecker().getVrCoreCompatibility();
180 
181         String infobarText;
182         String buttonText;
183 
184         if (vrCoreCompatibility == VrCoreVersionChecker.VrCoreCompatibility.VR_NOT_AVAILABLE) {
185             // Supported, but not installed. Ask user to install instead of upgrade.
186             infobarText = ContextUtils.getApplicationContext().getString(
187                     org.chromium.chrome.vr.R.string.vr_services_check_infobar_install_text);
188             buttonText = ContextUtils.getApplicationContext().getString(
189                     org.chromium.chrome.vr.R.string.vr_services_check_infobar_install_button);
190         } else if (vrCoreCompatibility == VrCoreVersionChecker.VrCoreCompatibility.VR_OUT_OF_DATE) {
191             infobarText = ContextUtils.getApplicationContext().getString(
192                     org.chromium.chrome.vr.R.string.vr_services_check_infobar_update_text);
193             buttonText = ContextUtils.getApplicationContext().getString(
194                     org.chromium.chrome.vr.R.string.vr_services_check_infobar_update_button);
195         } else {
196             Log.e(TAG, "Unknown VrCore compatibility: " + vrCoreCompatibility);
197             return;
198         }
199 
200         SimpleConfirmInfoBarBuilder.Listener listener = new SimpleConfirmInfoBarBuilder.Listener() {
201             @Override
202             public void onInfoBarDismissed() {
203                 maybeNotifyNativeOnInstallResult(false);
204             }
205 
206             @Override
207             public boolean onInfoBarButtonClicked(boolean isPrimary) {
208                 assert sRequestInstallInstance == null;
209                 sRequestInstallInstance = VrCoreInstallUtils.this;
210                 activity.startActivityForResult(
211                         new Intent(Intent.ACTION_VIEW, Uri.parse(VR_CORE_MARKET_URI)),
212                         VR_SERVICES_UPDATE_RESULT);
213                 return false;
214             }
215 
216             @Override
217             public boolean onInfoBarLinkClicked() {
218                 return false;
219             }
220         };
221         SimpleConfirmInfoBarBuilder.create(webContents, listener,
222                 InfoBarIdentifier.VR_SERVICES_UPGRADE_ANDROID, activity,
223                 org.chromium.chrome.vr.R.drawable.vr_services, infobarText, buttonText, null, null,
224                 true);
225     }
226 
onVrCoreMaybeUpdated()227     private void onVrCoreMaybeUpdated() {
228         maybeNotifyNativeOnInstallResult(updateVrSupportLevel() != VrSupportLevel.VR_NEEDS_UPDATE);
229     }
230 
231     /**
232      * Helper used to notify native code about the result of the request to install VRCore.
233      */
maybeNotifyNativeOnInstallResult(boolean success)234     private void maybeNotifyNativeOnInstallResult(boolean success) {
235         if (mNativeVrCoreInstallUtils != 0) {
236             VrCoreInstallUtilsJni.get().onInstallResult(mNativeVrCoreInstallUtils, success);
237         }
238     }
239 
240     @NativeMethods
241     interface Natives {
onInstallResult(long nativeVrCoreInstallHelper, boolean success)242         void onInstallResult(long nativeVrCoreInstallHelper, boolean success);
243     }
244 }
245