Files
Lawnchair/src/com/android/launcher3/states/RotationHelper.java
T
Vinit Nayak b9ec9319c5 Add fixed_rotation_transform to home settings
This sets the feature flag on launcher side
and also updates the setting in Settings.Global
Launcher DOES NOT listen to the Settings.Global
change from adb anymore. This should take
preference over setting it from command line.

Also fix a related swipe to home animation bug
that happened w/ merge conflict.

Fixes: 150260456
Test: Set and unset, visually verified behavior.
Tested w/ autorotate on and off.
Checked Settings.Global value correctly updated
via "adb shell settings get global
fixed_rotation_transform"
TODO: Update tests to reflect this new
default-on fixed rotation behavior.

Change-Id: Id95f006eb1e92a59e24b05567298fd21b1409b13
2020-03-11 19:38:33 -07:00

428 lines
16 KiB
Java

/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.states;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.provider.Settings;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.WindowManager;
import com.android.launcher3.Launcher;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.UiThreadHelper;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class to manage launcher rotation
*/
public class RotationHelper implements OnSharedPreferenceChangeListener {
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
private final ContentResolver mContentResolver;
/**
* Listener to receive changes when {@link #FIXED_ROTATION_TRANSFORM_SETTING_NAME} flag changes.
*/
public interface ForcedRotationChangedListener {
void onForcedRotationChanged(boolean isForcedRotation);
}
public static boolean getAllowRotationDefaultValue() {
// If the device's pixel density was scaled (usually via settings for A11y), use the
// original dimensions to determine if rotation is allowed of not.
Resources res = Resources.getSystem();
int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
* res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
return originalSmallestWidth >= 600;
}
public static final int REQUEST_NONE = 0;
public static final int REQUEST_ROTATE = 1;
public static final int REQUEST_LOCK = 2;
private final Launcher mLauncher;
private final SharedPreferences mSharedPrefs;
private final SharedPreferences mFeatureFlagsPrefs;
private boolean mIgnoreAutoRotateSettings;
private boolean mAutoRotateEnabled;
private boolean mForcedRotation;
private List<ForcedRotationChangedListener> mForcedRotationChangedListeners = new ArrayList<>();
/**
* Rotation request made by
* {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}.
* This supersedes any other request.
*/
private int mStateHandlerRequest = REQUEST_NONE;
/**
* Rotation request made by an app transition
*/
private int mCurrentTransitionRequest = REQUEST_NONE;
/**
* Rotation request made by a Launcher State
*/
private int mCurrentStateRequest = REQUEST_NONE;
// This is used to defer setting rotation flags until the activity is being created
private boolean mInitialized;
private boolean mDestroyed;
private boolean mRotationHasDifferentUI;
private int mLastActivityFlags = -1;
public RotationHelper(Launcher launcher) {
mLauncher = launcher;
// On large devices we do not handle auto-rotate differently.
mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
if (!mIgnoreAutoRotateSettings) {
mSharedPrefs = Utilities.getPrefs(mLauncher);
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
} else {
mSharedPrefs = null;
}
mContentResolver = launcher.getContentResolver();
mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mLauncher);
mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
updateForcedRotation(true);
}
/**
* @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
* from the home developer settings. Otherwise it will not.
* This is primarily to allow tests to set their own conditions.
*/
private void updateForcedRotation(boolean setValueFromPrefs) {
boolean isForcedRotation = mFeatureFlagsPrefs
.getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
&& !getAllowRotationDefaultValue();
if (mForcedRotation == isForcedRotation) {
return;
}
if (setValueFromPrefs) {
mForcedRotation = isForcedRotation;
}
UI_HELPER_EXECUTOR.execute(
() -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
mForcedRotation ? 1 : 0));
for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
listener.onForcedRotationChanged(mForcedRotation);
}
}
/**
* will not be called when first registering the listener.
*/
public void addForcedRotationCallback(ForcedRotationChangedListener listener) {
mForcedRotationChangedListeners.add(listener);
}
public void removeForcedRotationCallback(ForcedRotationChangedListener listener) {
mForcedRotationChangedListeners.remove(listener);
}
public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) {
mRotationHasDifferentUI = rotationHasDifferentUI;
}
public boolean homeScreenCanRotate() {
return mRotationHasDifferentUI || mIgnoreAutoRotateSettings || mAutoRotateEnabled
|| mStateHandlerRequest != REQUEST_NONE
|| mLauncher.getDeviceProfile().isMultiWindowMode;
}
public void updateRotationAnimation() {
if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
WindowManager.LayoutParams lp = mLauncher.getWindow().getAttributes();
int oldAnim = lp.rotationAnimation;
lp.rotationAnimation = homeScreenCanRotate()
? WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE
: WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
if (oldAnim != lp.rotationAnimation) {
mLauncher.getWindow().setAttributes(lp);
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
updateForcedRotation(true);
return;
}
boolean wasRotationEnabled = mAutoRotateEnabled;
mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
if (mAutoRotateEnabled != wasRotationEnabled) {
notifyChange();
updateRotationAnimation();
mLauncher.reapplyUi();
}
}
public void setStateHandlerRequest(int request) {
if (mStateHandlerRequest != request) {
mStateHandlerRequest = request;
updateRotationAnimation();
notifyChange();
}
}
public void setCurrentTransitionRequest(int request) {
if (mCurrentTransitionRequest != request) {
mCurrentTransitionRequest = request;
notifyChange();
}
}
public void setCurrentStateRequest(int request) {
if (mCurrentStateRequest != request) {
mCurrentStateRequest = request;
notifyChange();
}
}
// Used by tests only.
public void forceAllowRotationForTesting(boolean allowRotation) {
mIgnoreAutoRotateSettings =
allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
// TODO(b/150214193) Tests currently expect launcher to be able to be rotated
// Modify tests for this new behavior
mForcedRotation = !allowRotation;
updateForcedRotation(false);
notifyChange();
}
public void initialize() {
if (!mInitialized) {
mInitialized = true;
notifyChange();
updateRotationAnimation();
}
}
public void destroy() {
if (!mDestroyed) {
mDestroyed = true;
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
mForcedRotationChangedListeners.clear();
mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
}
private void notifyChange() {
if (!mInitialized || mDestroyed) {
return;
}
final int activityFlags;
if (mForcedRotation) {
// TODO(b/150214193) Properly address this
activityFlags = SCREEN_ORIENTATION_PORTRAIT;
} else if (mStateHandlerRequest != REQUEST_NONE) {
activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentTransitionRequest != REQUEST_NONE) {
activityFlags = mCurrentTransitionRequest == REQUEST_LOCK ?
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentStateRequest == REQUEST_LOCK) {
activityFlags = SCREEN_ORIENTATION_LOCKED;
} else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
|| mAutoRotateEnabled) {
activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
} else {
// If auto rotation is off, allow rotation on the activity, in case the user is using
// forced rotation.
activityFlags = SCREEN_ORIENTATION_NOSENSOR;
}
if (activityFlags != mLastActivityFlags) {
mLastActivityFlags = activityFlags;
UiThreadHelper.setOrientationAsync(mLauncher, activityFlags);
}
}
public static int getDegreesFromRotation(int rotation) {
int degrees;
switch (rotation) {
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
case Surface.ROTATION_0:
default:
degrees = 0;
break;
}
return degrees;
}
public static int getRotationFromDegrees(float degrees) {
int threshold = 70;
if (degrees >= (360 - threshold) || degrees < (threshold)) {
return Surface.ROTATION_0;
} else if (degrees < (90 + threshold)) {
return Surface.ROTATION_270;
} else if (degrees < 180 + threshold) {
return Surface.ROTATION_180;
} else {
return Surface.ROTATION_90;
}
}
/**
* @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
* E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
* A value of 0 means no rotation has been applied
*/
public static int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
return delta;
}
/**
* For landscape, since the navbar is already in a vertical position, we don't have to do any
* rotations as the change in Y coordinate is what is read. We only flip the sign of the
* y coordinate to make it match existing behavior of swipe to the top to go previous
*/
public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
// TODO(b/151269990): Use a temp matrix
Matrix m = new Matrix();
m.setScale(1, -1);
if (inverse) {
Matrix inv = new Matrix();
m.invert(inv);
ev.transform(inv);
} else {
ev.transform(m);
}
}
/**
* Creates a matrix to transform the given motion event specified by degrees.
* If {@param inverse} is {@code true}, the inverse of that matrix will be applied
*/
public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
Matrix transform = new Matrix();
// TODO(b/151269990): Use a temp matrix
transform.setRotate(degrees);
if (inverse) {
Matrix inv = new Matrix();
transform.invert(inv);
ev.transform(inv);
} else {
ev.transform(transform);
}
// TODO: Add scaling back in based on degrees
// if (getWidth() > 0 && getHeight() > 0) {
// float scale = ((float) getWidth()) / getHeight();
// transform.postScale(scale, 1 / scale);
// }
}
/**
* TODO(b/149658423): Have {@link com.android.quickstep.OrientationTouchTransformer
* also use this}
*/
public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
Matrix m = new Matrix();
// TODO(b/151269990): Use a temp matrix
switch (displayRotation) {
case Surface.ROTATION_0:
return m;
case Surface.ROTATION_90:
m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
m.postTranslate(0, screenWidth);
break;
case Surface.ROTATION_270:
m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
m.postTranslate(screenHeight, 0);
break;
}
return m;
}
public static void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight,
int displayRotation) {
Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
m.mapRect(src);
}
public static void mapInverseRectFromNormalOrientation(RectF src, int screenWidth,
int screenHeight, int displayRotation) {
Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
Matrix inverse = new Matrix();
m.invert(inverse);
inverse.mapRect(src);
}
public static void getTargetRectForRotation(Rect srcOut, int screenWidth, int screenHeight,
int displayRotation) {
RectF wrapped = new RectF(srcOut);
Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
m.mapRect(wrapped);
wrapped.round(srcOut);
}
public static boolean isRotationLandscape(int rotation) {
return rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90;
}
@Override
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
+ " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mAutoRotateEnabled=%b]",
mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
mIgnoreAutoRotateSettings, mAutoRotateEnabled);
}
}