| // Copyright 2014 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.Context; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.PackageManager; |
| import android.util.Pair; |
| import android.view.Surface; |
| |
| import androidx.annotation.Nullable; |
| |
| 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.browser.WebContents; |
| import org.chromium.device.mojom.ScreenOrientationLockType; |
| 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 |
| implements ActivityStateListener, ScreenOrientationProvider { |
| private static class Holder { |
| private static ScreenOrientationProviderImpl sInstance = |
| new ScreenOrientationProviderImpl(); |
| } |
| |
| private static final String TAG = "ScreenOrientation"; |
| |
| // More readable constants to be passed to |addPendingRequest|. |
| private static final boolean LOCK = true; |
| private static final boolean UNLOCK = false; |
| |
| private ScreenOrientationDelegate mDelegate; |
| |
| /** |
| * The keys of the map are the activities for which screen orientation are |
| * trying to lock. |
| * The values of the map are the most recent default web screen orientation request for each |
| * activity. |
| */ |
| private Map<Activity, Byte> mDefaultOrientationOverrides = new WeakHashMap<>(); |
| |
| /** |
| * 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<>(); |
| |
| private static final class PendingRequest implements WindowEventObserver { |
| private final ScreenOrientationProviderImpl mProvider; |
| private final WindowEventObserverManager mWindowEventManager; |
| private final boolean mLockOrUnlock; |
| private final byte mWebScreenOrientation; |
| private boolean mObserverRemoved; |
| |
| public PendingRequest(ScreenOrientationProviderImpl provider, |
| WindowEventObserverManager windowEventManager, boolean lockOrUnlock, |
| byte webScreenOrientation) { |
| mProvider = provider; |
| mWindowEventManager = windowEventManager; |
| mLockOrUnlock = lockOrUnlock; |
| mWebScreenOrientation = webScreenOrientation; |
| mWindowEventManager.addObserver(this); |
| } |
| |
| public void cancel() { |
| if (mObserverRemoved) return; |
| mWindowEventManager.removeObserver(this); |
| mObserverRemoved = true; |
| } |
| |
| @Override |
| public void onWindowAndroidChanged(WindowAndroid newWindowAndroid) { |
| if (newWindowAndroid == null) return; |
| |
| if (mLockOrUnlock) { |
| mProvider.lockOrientation(newWindowAndroid, mWebScreenOrientation); |
| } else { |
| mProvider.unlockOrientation(newWindowAndroid); |
| } |
| mWindowEventManager.removeObserver(this); |
| mObserverRemoved = true; |
| } |
| } |
| |
| private final Map<WebContents, PendingRequest> mPendingRequests = new WeakHashMap<>(); |
| |
| @CalledByNative |
| public static ScreenOrientationProviderImpl getInstance() { |
| return Holder.sInstance; |
| } |
| |
| private static int getOrientationFromWebScreenOrientations(byte orientation, |
| @Nullable WindowAndroid window, Context context) { |
| switch (orientation) { |
| case ScreenOrientationLockType.DEFAULT: |
| return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; |
| case ScreenOrientationLockType.PORTRAIT_PRIMARY: |
| return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; |
| case ScreenOrientationLockType.PORTRAIT_SECONDARY: |
| return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; |
| case ScreenOrientationLockType.LANDSCAPE_PRIMARY: |
| return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; |
| case ScreenOrientationLockType.LANDSCAPE_SECONDARY: |
| return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; |
| case ScreenOrientationLockType.PORTRAIT: |
| return ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; |
| case ScreenOrientationLockType.LANDSCAPE: |
| return ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; |
| case ScreenOrientationLockType.ANY: |
| return ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; |
| case ScreenOrientationLockType.NATURAL: |
| // If the tab is being reparented, we don't have a display strongly associated with |
| // it, so we get the default display. |
| DisplayAndroid displayAndroid = (window != null) ? window.getDisplay() |
| : DisplayAndroid.getNonMultiDisplay(context); |
| int rotation = displayAndroid.getRotation(); |
| if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { |
| if (displayAndroid.getDisplayHeight() >= displayAndroid.getDisplayWidth()) { |
| return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; |
| } |
| return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; |
| } else { |
| if (displayAndroid.getDisplayHeight() < displayAndroid.getDisplayWidth()) { |
| return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; |
| } |
| return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; |
| } |
| default: |
| Log.w(TAG, "Trying to lock to unsupported orientation!"); |
| return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; |
| } |
| } |
| |
| @Override |
| public void onActivityStateChange(Activity activity, @ActivityState int newState) { |
| if (newState == ActivityState.DESTROYED) { |
| mDelayedRequests.remove(activity); |
| } |
| } |
| |
| private void addPendingRequest( |
| WebContents webContents, boolean lockOrUnlock, byte webScreenOrientation) { |
| WindowEventObserverManager windowEventManager = |
| WindowEventObserverManager.from(webContents); |
| PendingRequest existingRequest = mPendingRequests.get(webContents); |
| if (existingRequest != null) existingRequest.cancel(); |
| mPendingRequests.put(webContents, |
| new PendingRequest(this, windowEventManager, lockOrUnlock, webScreenOrientation)); |
| } |
| |
| @CalledByNative |
| private void lockOrientationForWebContents(WebContents webContents, byte webScreenOrientation) { |
| WindowAndroid window = webContents.getTopLevelNativeWindow(); |
| if (window == null) { |
| addPendingRequest(webContents, LOCK, webScreenOrientation); |
| } else { |
| lockOrientation(window, webScreenOrientation); |
| } |
| } |
| |
| @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(); |
| |
| // Locking orientation is only supported for web contents that have an associated activity. |
| // Note that we can't just use the focused activity, as that would lead to bugs where |
| // unlockOrientation unlocks a different activity to the one that was locked. |
| if (activity == null) return; |
| |
| int orientation = getOrientationFromWebScreenOrientations(webScreenOrientation, window, |
| activity); |
| if (orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { |
| return; |
| } |
| |
| setMaybeDelayedRequestedOrientation(activity, true /* lock */, orientation); |
| } |
| |
| @CalledByNative |
| private void unlockOrientationForWebContents(WebContents webContents) { |
| WindowAndroid window = webContents.getTopLevelNativeWindow(); |
| if (window == null) { |
| addPendingRequest(webContents, UNLOCK, (byte) 0); |
| } else { |
| unlockOrientation(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(); |
| |
| // Locking orientation is only supported for web contents that have an associated activity. |
| // Note that we can't just use the focused activity, as that would lead to bugs where |
| // unlockOrientation unlocks a different activity to the one that was locked. |
| if (activity == null) return; |
| byte mDefaultWebOrientation = (byte) ScreenOrientationLockType.DEFAULT; |
| if (mDefaultOrientationOverrides.containsKey(activity)) { |
| mDefaultWebOrientation = mDefaultOrientationOverrides.get(activity); |
| } |
| |
| int defaultOrientation = |
| getOrientationFromWebScreenOrientations(mDefaultWebOrientation, window, activity); |
| |
| try { |
| if (defaultOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { |
| ActivityInfo info = activity.getPackageManager().getActivityInfo( |
| activity.getComponentName(), PackageManager.GET_META_DATA); |
| defaultOrientation = info.screenOrientation; |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| // Do nothing, defaultOrientation should be SCREEN_ORIENTATION_UNSPECIFIED. |
| } finally { |
| 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 |
| public boolean isOrientationLockEnabled() { |
| return mDelegate == null || mDelegate.canLockOrientation(); |
| } |
| |
| @Override |
| public void setOrientationDelegate(ScreenOrientationDelegate delegate) { |
| mDelegate = delegate; |
| } |
| |
| @Override |
| public void setOverrideDefaultOrientation(WindowAndroid window, byte defaultWebOrientation) { |
| if (window == null) return; |
| Activity activity = window.getActivity().get(); |
| |
| if (activity == null) return; |
| |
| if (defaultWebOrientation != ScreenOrientationLockType.DEFAULT) { |
| mDefaultOrientationOverrides.put(activity, defaultWebOrientation); |
| } else { |
| mDefaultOrientationOverrides.remove(activity); |
| } |
| } |
| |
| /** 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); |
| } |
| } |