Commit b9004e2b authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android WebAPK] Delay locking screen orientation for new-style WebAPKs 1/2

Android throws an IllegalStateException when
Activity#setRequestedOrientation() while the activity is translucent.
This CL:
- Delays setting the screen orientation in WebappActivity till the
activity's translucency is removed
- Adds a method to SplashscreenObserver to notify that translucency was
removed.

NOPRESUBMIT=true
BUG=957835

Change-Id: I5971ec6b97f6ad50f71dd6e87f3fd19ecb7681db
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1589105
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659046}
parent 18d556b8
......@@ -1244,7 +1244,7 @@ public class VrShellDelegate
if (mActivity.getCompositorViewHolder() != null) {
mActivity.getCompositorViewHolder().onEnterVr();
}
ScreenOrientationProvider.setOrientationDelegate(this);
ScreenOrientationProvider.getInstance().setOrientationDelegate(this);
// Hide system UI.
VrModuleProvider.getDelegate().setSystemUiVisibilityForVr(mActivity);
......@@ -1263,7 +1263,7 @@ public class VrShellDelegate
@TargetApi(Build.VERSION_CODES.KITKAT)
private void restoreWindowMode() {
ScreenOrientationProvider.setOrientationDelegate(null);
ScreenOrientationProvider.getInstance().setOrientationDelegate(null);
mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Restore orientation.
......
......@@ -17,6 +17,9 @@ public class WebApkSplashscreenMetrics implements SplashscreenObserver {
mShellApkLaunchTimeMs = shellApkLaunchTimeMs;
}
@Override
public void onTranslucencyRemoved() {}
@Override
public void onSplashscreenHidden(long startTimestamp, long endTimestamp) {
if (mShellApkLaunchTimeMs == -1) return;
......
......@@ -126,7 +126,7 @@ public class ArImmersiveOverlay implements SurfaceHolder.Callback2,
}
// Save current orientation mode, and then lock current orientation.
ScreenOrientationProvider.setOrientationDelegate(this);
ScreenOrientationProvider.getInstance().setOrientationDelegate(this);
if (mRestoreOrientation == null) {
mRestoreOrientation = mActivity.getRequestedOrientation();
}
......@@ -160,7 +160,7 @@ public class ArImmersiveOverlay implements SurfaceHolder.Callback2,
mCleanupInProgress = true;
// Restore orientation.
ScreenOrientationProvider.setOrientationDelegate(null);
ScreenOrientationProvider.getInstance().setOrientationDelegate(null);
if (mRestoreOrientation != null) mActivity.setRequestedOrientation(mRestoreOrientation);
mRestoreOrientation = null;
......
......@@ -8,6 +8,9 @@ package org.chromium.chrome.browser.webapps;
* Observer interface for WebApp activity splashscreen.
*/
public interface SplashscreenObserver {
/** Called when the activity's translucency is removed. */
void onTranslucencyRemoved();
/**
* Called when the splash screen is hidden.
* @param startTimestamp Time that the splash screen was shown.
......
......@@ -294,9 +294,6 @@ public class WebappActivity extends SingleTabActivity {
StrictMode.setThreadPolicy(oldPolicy);
}
ScreenOrientationProvider.lockOrientation(
getWindowAndroid(), (byte) mWebappInfo.orientation());
// When turning on TalkBack on Android, hitting app switcher to bring a WebappActivity to
// front will speak "Web App", which is the label of WebappActivity. Therefore, we set title
// of the WebappActivity explicitly to make it speak the short name of the Web App.
......@@ -304,6 +301,8 @@ public class WebappActivity extends SingleTabActivity {
super.performPreInflationStartup();
applyScreenOrientation();
if (mWebappInfo.displayMode() == WebDisplayMode.FULLSCREEN) {
enterImmersiveMode();
}
......@@ -889,6 +888,32 @@ public class WebappActivity extends SingleTabActivity {
mSplashController.setDelegate(delegate);
}
/** Sets the screen orientation. */
private void applyScreenOrientation() {
if (mWebappInfo.isSplashProvidedByWebApk()) {
// When the splash screen is provided by the WebAPK, the activity is initially
// translucent. Setting the screen orientation while the activity is translucent
// throws an exception. Delay setting it.
ScreenOrientationProvider.getInstance().delayOrientationRequests(getWindowAndroid());
addSplashscreenObserver(new SplashscreenObserver() {
@Override
public void onTranslucencyRemoved() {
ScreenOrientationProvider.getInstance().runDelayedOrientationRequests(
getWindowAndroid());
}
@Override
public void onSplashscreenHidden(long startTimestamp, long endTimestamp) {}
});
// Fall through and queue up request for the default screen orientation because the web
// page might change it via JavaScript.
}
ScreenOrientationProvider.getInstance().lockOrientation(
getWindowAndroid(), (byte) mWebappInfo.orientation());
}
/**
* Register an observer to the splashscreen hidden/visible events for this activity.
*/
......
......@@ -4,6 +4,7 @@
#include "content/browser/screen_orientation/screen_orientation_delegate_android.h"
#include "base/android/scoped_java_ref.h"
#include "content/browser/screen_orientation/screen_orientation_provider.h"
#include "jni/ScreenOrientationProviderImpl_jni.h"
#include "ui/android/window_android.h"
......@@ -27,25 +28,33 @@ bool ScreenOrientationDelegateAndroid::FullScreenRequired(
void ScreenOrientationDelegateAndroid::Lock(
WebContents* web_contents,
blink::WebScreenOrientationLockType lock_orientation) {
base::android::ScopedJavaLocalRef<jobject> java_instance =
Java_ScreenOrientationProviderImpl_getInstance(
base::android::AttachCurrentThread());
gfx::NativeWindow window = web_contents->GetTopLevelNativeWindow();
Java_ScreenOrientationProviderImpl_lockOrientation(
base::android::AttachCurrentThread(),
window ? window->GetJavaObject() : nullptr,
lock_orientation);
base::android::AttachCurrentThread(), java_instance,
window ? window->GetJavaObject() : nullptr, lock_orientation);
}
bool ScreenOrientationDelegateAndroid::ScreenOrientationProviderSupported() {
// TODO(MLamouri): Consider moving isOrientationLockEnabled to a separate
// function, so reported error messages can differentiate between the device
// never supporting orientation or currently not support orientation.
base::android::ScopedJavaLocalRef<jobject> java_instance =
Java_ScreenOrientationProviderImpl_getInstance(
base::android::AttachCurrentThread());
return Java_ScreenOrientationProviderImpl_isOrientationLockEnabled(
base::android::AttachCurrentThread());
base::android::AttachCurrentThread(), java_instance);
}
void ScreenOrientationDelegateAndroid::Unlock(WebContents* web_contents) {
base::android::ScopedJavaLocalRef<jobject> java_instance =
Java_ScreenOrientationProviderImpl_getInstance(
base::android::AttachCurrentThread());
gfx::NativeWindow window = web_contents->GetTopLevelNativeWindow();
Java_ScreenOrientationProviderImpl_unlockOrientation(
base::android::AttachCurrentThread(),
base::android::AttachCurrentThread(), java_instance,
window ? window->GetJavaObject() : nullptr);
}
......
......@@ -564,6 +564,7 @@ junit_binary("content_junit_tests") {
java_files = [
"junit/src/org/chromium/content/browser/BindingManagerTest.java",
"junit/src/org/chromium/content/browser/ChildProcessRankingTest.java",
"junit/src/org/chromium/content/browser/ScreenOrientationProviderImplTest.java",
"junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java",
"junit/src/org/chromium/content/browser/accessibility/BrowserAccessibilityStateTest.java",
"junit/src/org/chromium/content/browser/SpareChildConnectionTest.java",
......
......@@ -9,24 +9,53 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.Surface;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ApplicationStatus.ActivityStateListener;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.content_public.browser.ScreenOrientationDelegate;
import org.chromium.content_public.browser.ScreenOrientationProvider;
import org.chromium.content_public.common.ScreenOrientationConstants;
import org.chromium.content_public.common.ScreenOrientationValues;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.display.DisplayAndroid;
import java.util.Map;
import java.util.WeakHashMap;
/**
* This is the implementation of the C++ counterpart ScreenOrientationProvider.
*/
@JNINamespace("content")
public class ScreenOrientationProviderImpl {
public class ScreenOrientationProviderImpl
implements ActivityStateListener, ScreenOrientationProvider {
private static class Holder {
private static ScreenOrientationProviderImpl sInstance =
new ScreenOrientationProviderImpl();
}
private static final String TAG = "cr.ScreenOrientation";
private static ScreenOrientationDelegate sDelegate;
private ScreenOrientationDelegate mDelegate;
/**
* The keys of the map are the activities for which screen orientation requests are
* delayed.
* The values of the map are the most recent screen orientation request for each activity.
* The map will contain an entry with a null value if screen orientation requests are delayed
* for an activity but no screen orientation requests have been made for the activity.
*/
private Map<Activity, Pair<Boolean, Integer>> mDelayedRequests = new WeakHashMap<>();
@CalledByNative
public static ScreenOrientationProviderImpl getInstance() {
return Holder.sInstance;
}
private static int getOrientationFromWebScreenOrientations(byte orientation,
@Nullable WindowAndroid window, Context context) {
......@@ -70,10 +99,16 @@ public class ScreenOrientationProviderImpl {
}
}
@CalledByNative
public static void lockOrientation(@Nullable WindowAndroid window, byte webScreenOrientation) {
if (sDelegate != null && !sDelegate.canLockOrientation()) return;
@Override
public void onActivityStateChange(Activity activity, @ActivityState int newState) {
if (newState == ActivityState.DESTROYED) {
mDelayedRequests.remove(activity);
}
}
@CalledByNative
@Override
public void lockOrientation(@Nullable WindowAndroid window, byte webScreenOrientation) {
// WindowAndroid may be null if the tab is being reparented.
if (window == null) return;
Activity activity = window.getActivity().get();
......@@ -89,11 +124,12 @@ public class ScreenOrientationProviderImpl {
return;
}
activity.setRequestedOrientation(orientation);
setMaybeDelayedRequestedOrientation(activity, true /* lock */, orientation);
}
@CalledByNative
public static void unlockOrientation(@Nullable WindowAndroid window) {
@Override
public void unlockOrientation(@Nullable WindowAndroid window) {
// WindowAndroid may be null if the tab is being reparented.
if (window == null) return;
Activity activity = window.getActivity().get();
......@@ -122,20 +158,71 @@ public class ScreenOrientationProviderImpl {
} catch (PackageManager.NameNotFoundException e) {
// Do nothing, defaultOrientation should be SCREEN_ORIENTATION_UNSPECIFIED.
} finally {
if (sDelegate == null || sDelegate.canUnlockOrientation(activity, defaultOrientation)) {
activity.setRequestedOrientation(defaultOrientation);
}
setMaybeDelayedRequestedOrientation(activity, false /* lock */, defaultOrientation);
}
}
@Override
public void delayOrientationRequests(WindowAndroid window) {
Activity activity = window.getActivity().get();
if ((activity == null || areRequestsDelayedForActivity(activity))) {
return;
}
mDelayedRequests.put(activity, null);
ApplicationStatus.registerStateListenerForActivity(this, activity);
}
@Override
public void runDelayedOrientationRequests(WindowAndroid window) {
Activity activity = window.getActivity().get();
if ((activity == null || !areRequestsDelayedForActivity(activity))) {
return;
}
Pair<Boolean, Integer> delayedRequest = mDelayedRequests.remove(activity);
if (delayedRequest != null) {
setRequestedOrientationNow(activity, delayedRequest.first, delayedRequest.second);
}
if (mDelayedRequests.isEmpty()) {
ApplicationStatus.unregisterActivityStateListener(this);
}
}
@CalledByNative
private static boolean isOrientationLockEnabled() {
return sDelegate == null || sDelegate.canLockOrientation();
public boolean isOrientationLockEnabled() {
return mDelegate == null || mDelegate.canLockOrientation();
}
public static void setOrientationDelegate(ScreenOrientationDelegate delegate) {
sDelegate = delegate;
@Override
public void setOrientationDelegate(ScreenOrientationDelegate delegate) {
mDelegate = delegate;
}
private ScreenOrientationProviderImpl() {}
/** Returns whether screen orientation requests are delayed for the passed-in activity. */
private boolean areRequestsDelayedForActivity(Activity activity) {
return mDelayedRequests.containsKey(activity);
}
/** Sets the requested orientation for the activity delaying the request if needed. */
private void setMaybeDelayedRequestedOrientation(
Activity activity, boolean lock, int orientation) {
if (areRequestsDelayedForActivity(activity)) {
mDelayedRequests.put(activity, Pair.create(lock, orientation));
} else {
setRequestedOrientationNow(activity, lock, orientation);
}
}
/** Sets the requested orientation for the activity. */
private void setRequestedOrientationNow(Activity activity, boolean lock, int orientation) {
if (mDelegate != null) {
if ((lock && !mDelegate.canLockOrientation())
|| (!lock && !mDelegate.canUnlockOrientation(activity, orientation))) {
return;
}
}
activity.setRequestedOrientation(orientation);
}
}
......@@ -12,23 +12,25 @@ import org.chromium.ui.base.WindowAndroid;
/**
* Interface providing the access to C++ ScreenOrientationProvider.
*/
public final class ScreenOrientationProvider {
private ScreenOrientationProvider() {}
public interface ScreenOrientationProvider {
static ScreenOrientationProvider getInstance() {
return ScreenOrientationProviderImpl.getInstance();
}
/**
* Locks screen rotation to a given orientation.
* @param window Window to lock rotation on.
* @param webScreenOrientation Screen orientation.
*/
public static void lockOrientation(@Nullable WindowAndroid window, byte webScreenOrientation) {
ScreenOrientationProviderImpl.lockOrientation(window, webScreenOrientation);
}
void lockOrientation(@Nullable WindowAndroid window, byte webScreenOrientation);
public static void unlockOrientation(@Nullable WindowAndroid window) {
ScreenOrientationProviderImpl.unlockOrientation(window);
}
void unlockOrientation(@Nullable WindowAndroid window);
public static void setOrientationDelegate(ScreenOrientationDelegate delegate) {
ScreenOrientationProviderImpl.setOrientationDelegate(delegate);
}
/** Delays screen orientation requests for the given window. */
void delayOrientationRequests(WindowAndroid window);
/** Runs delayed screen orientation requests for the given window. */
void runDelayedOrientationRequests(WindowAndroid window);
void setOrientationDelegate(ScreenOrientationDelegate delegate);
}
......@@ -231,7 +231,7 @@ public class ScreenOrientationListenerTest {
int callCount = mCallbackHelper.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(() -> {
ScreenOrientationProvider.lockOrientation(
ScreenOrientationProvider.getInstance().lockOrientation(
mActivityTestRule.getWebContents().getTopLevelNativeWindow(),
(byte) orientationValue);
});
......
// Copyright 2019 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.content.browser;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.content_public.common.ScreenOrientationValues;
import org.chromium.ui.base.ActivityWindowAndroid;
import java.lang.ref.WeakReference;
/** Unit tests for {@link ScreenOrientationProviderImpl } */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public final class ScreenOrientationProviderImplTest {
/**
* Tests that when screen orientation requests are delayed that newer requests overwrite older
* requests for a given activity.
*/
@Test
public void testDelayRequests() {
final Activity activity = Robolectric.buildActivity(Activity.class).create().get();
ActivityWindowAndroid window = buildMockWindowForActivity(activity);
// Last orientation lock request should take precedence.
ScreenOrientationProviderImpl instance = ScreenOrientationProviderImpl.getInstance();
instance.delayOrientationRequests(window);
instance.lockOrientation(window, (byte) ScreenOrientationValues.PORTRAIT_PRIMARY);
instance.lockOrientation(window, (byte) ScreenOrientationValues.LANDSCAPE_PRIMARY);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, activity.getRequestedOrientation());
instance.runDelayedOrientationRequests(window);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, activity.getRequestedOrientation());
// Lock then unlock screen orientation while requests are delayed.
instance.delayOrientationRequests(window);
instance.lockOrientation(window, (byte) ScreenOrientationValues.PORTRAIT_PRIMARY);
instance.unlockOrientation(window);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, activity.getRequestedOrientation());
instance.runDelayedOrientationRequests(window);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, activity.getRequestedOrientation());
}
/**
* Tests that whether screen orientation requests are delayed can be toggled for each activity
* independently.
*/
@Test
public void testDelayRequestsAppliesOnlyToActivity() {
final Activity activity1 = Robolectric.buildActivity(Activity.class).create().get();
ActivityWindowAndroid window1 = buildMockWindowForActivity(activity1);
final Activity activity2 = Robolectric.buildActivity(Activity.class).create().get();
ActivityWindowAndroid window2 = buildMockWindowForActivity(activity2);
ScreenOrientationProviderImpl instance = ScreenOrientationProviderImpl.getInstance();
instance.delayOrientationRequests(window1);
instance.lockOrientation(window1, (byte) ScreenOrientationValues.PORTRAIT_PRIMARY);
instance.lockOrientation(window2, (byte) ScreenOrientationValues.LANDSCAPE_PRIMARY);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, activity1.getRequestedOrientation());
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, activity2.getRequestedOrientation());
instance.runDelayedOrientationRequests(window1);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, activity1.getRequestedOrientation());
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, activity2.getRequestedOrientation());
}
/**
* Tests that removing the screen orientation request delay is a no-op if there are no pending
* screen orientation requests.
*/
@Test
public void testRemoveDelayNoPendingRequests() {
final Activity activity = Robolectric.buildActivity(Activity.class).create().get();
ActivityWindowAndroid window = buildMockWindowForActivity(activity);
ScreenOrientationProviderImpl instance = ScreenOrientationProviderImpl.getInstance();
instance.delayOrientationRequests(window);
instance.runDelayedOrientationRequests(window);
Assert.assertEquals(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, activity.getRequestedOrientation());
}
private ActivityWindowAndroid buildMockWindowForActivity(Activity activity) {
ActivityWindowAndroid window = Mockito.mock(ActivityWindowAndroid.class);
Mockito.when(window.getActivity()).thenReturn(new WeakReference<>(activity));
return window;
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment