1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.states; 17 18 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; 19 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 20 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 21 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; 22 23 import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION; 24 import static com.android.launcher3.Utilities.dpiFromPx; 25 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 26 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH; 27 28 import android.app.Activity; 29 import android.content.Context; 30 import android.content.SharedPreferences; 31 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 32 import android.os.Handler; 33 import android.os.Message; 34 35 import androidx.annotation.Nullable; 36 import androidx.annotation.WorkerThread; 37 38 import com.android.launcher3.BaseActivity; 39 import com.android.launcher3.LauncherPrefs; 40 import com.android.launcher3.util.DisplayController; 41 42 /** 43 * Utility class to manage launcher rotation 44 */ 45 public class RotationHelper implements OnSharedPreferenceChangeListener, 46 DisplayController.DisplayInfoChangeListener { 47 48 public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation"; 49 50 /** 51 * Returns the default value of {@link #ALLOW_ROTATION_PREFERENCE_KEY} preference. 52 */ getAllowRotationDefaultValue(DisplayController.Info info)53 public static boolean getAllowRotationDefaultValue(DisplayController.Info info) { 54 // If the device's pixel density was scaled (usually via settings for A11y), use the 55 // original dimensions to determine if rotation is allowed of not. 56 float originalSmallestWidth = dpiFromPx(Math.min(info.currentSize.x, info.currentSize.y), 57 DENSITY_DEVICE_STABLE); 58 return originalSmallestWidth >= MIN_TABLET_WIDTH; 59 } 60 61 public static final int REQUEST_NONE = 0; 62 public static final int REQUEST_ROTATE = 1; 63 public static final int REQUEST_LOCK = 2; 64 65 @Nullable 66 private BaseActivity mActivity; 67 private final Handler mRequestOrientationHandler; 68 69 private boolean mIgnoreAutoRotateSettings; 70 private boolean mForceAllowRotationForTesting; 71 private boolean mHomeRotationEnabled; 72 73 /** 74 * Rotation request made by 75 * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}. 76 * This supersedes any other request. 77 */ 78 private int mStateHandlerRequest = REQUEST_NONE; 79 /** 80 * Rotation request made by an app transition 81 */ 82 private int mCurrentTransitionRequest = REQUEST_NONE; 83 /** 84 * Rotation request made by a Launcher State 85 */ 86 private int mCurrentStateRequest = REQUEST_NONE; 87 88 // This is used to defer setting rotation flags until the activity is being created 89 private boolean mInitialized; 90 private boolean mDestroyed; 91 92 // Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags 93 private int mLastActivityFlags = -999; 94 RotationHelper(BaseActivity activity)95 public RotationHelper(BaseActivity activity) { 96 mActivity = activity; 97 mRequestOrientationHandler = 98 new Handler(UI_HELPER_EXECUTOR.getLooper(), this::setOrientationAsync); 99 } 100 setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings, DisplayController.Info info)101 private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings, 102 DisplayController.Info info) { 103 // On large devices we do not handle auto-rotate differently. 104 mIgnoreAutoRotateSettings = ignoreAutoRotateSettings; 105 if (!mIgnoreAutoRotateSettings) { 106 mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION); 107 LauncherPrefs.get(mActivity).addListener(this, ALLOW_ROTATION); 108 } else { 109 LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION); 110 } 111 } 112 113 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s)114 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 115 if (mDestroyed || mIgnoreAutoRotateSettings) return; 116 boolean wasRotationEnabled = mHomeRotationEnabled; 117 mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION); 118 if (mHomeRotationEnabled != wasRotationEnabled) { 119 notifyChange(); 120 } 121 } 122 123 @Override onDisplayInfoChanged(Context context, DisplayController.Info info, int flags)124 public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) { 125 boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds); 126 if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) { 127 setIgnoreAutoRotateSettings(ignoreAutoRotateSettings, info); 128 notifyChange(); 129 } 130 } 131 setStateHandlerRequest(int request)132 public void setStateHandlerRequest(int request) { 133 if (mStateHandlerRequest != request) { 134 mStateHandlerRequest = request; 135 notifyChange(); 136 } 137 } 138 setCurrentTransitionRequest(int request)139 public void setCurrentTransitionRequest(int request) { 140 if (mCurrentTransitionRequest != request) { 141 mCurrentTransitionRequest = request; 142 notifyChange(); 143 } 144 } 145 setCurrentStateRequest(int request)146 public void setCurrentStateRequest(int request) { 147 if (mCurrentStateRequest != request) { 148 mCurrentStateRequest = request; 149 notifyChange(); 150 } 151 } 152 153 // Used by tests only. forceAllowRotationForTesting(boolean allowRotation)154 public void forceAllowRotationForTesting(boolean allowRotation) { 155 mForceAllowRotationForTesting = allowRotation; 156 notifyChange(); 157 } 158 initialize()159 public void initialize() { 160 if (!mInitialized) { 161 mInitialized = true; 162 DisplayController displayController = DisplayController.INSTANCE.get(mActivity); 163 DisplayController.Info info = displayController.getInfo(); 164 setIgnoreAutoRotateSettings(info.isTablet(info.realBounds), info); 165 displayController.addChangeListener(this); 166 notifyChange(); 167 } 168 } 169 destroy()170 public void destroy() { 171 if (!mDestroyed) { 172 mDestroyed = true; 173 DisplayController.INSTANCE.get(mActivity).removeChangeListener(this); 174 LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION); 175 mActivity = null; 176 } 177 } 178 notifyChange()179 private void notifyChange() { 180 if (!mInitialized || mDestroyed) { 181 return; 182 } 183 184 final int activityFlags; 185 if (mStateHandlerRequest != REQUEST_NONE) { 186 activityFlags = mStateHandlerRequest == REQUEST_LOCK ? 187 SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED; 188 } else if (mCurrentTransitionRequest != REQUEST_NONE) { 189 activityFlags = mCurrentTransitionRequest == REQUEST_LOCK ? 190 SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED; 191 } else if (mCurrentStateRequest == REQUEST_LOCK) { 192 activityFlags = SCREEN_ORIENTATION_LOCKED; 193 } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE 194 || mHomeRotationEnabled || mForceAllowRotationForTesting) { 195 activityFlags = SCREEN_ORIENTATION_UNSPECIFIED; 196 } else { 197 // If auto rotation is off, allow rotation on the activity, in case the user is using 198 // forced rotation. 199 activityFlags = SCREEN_ORIENTATION_NOSENSOR; 200 } 201 if (activityFlags != mLastActivityFlags) { 202 mLastActivityFlags = activityFlags; 203 mRequestOrientationHandler.sendEmptyMessage(activityFlags); 204 } 205 } 206 207 @WorkerThread setOrientationAsync(Message msg)208 private boolean setOrientationAsync(Message msg) { 209 Activity activity = mActivity; 210 if (activity != null) { 211 activity.setRequestedOrientation(msg.what); 212 } 213 return true; 214 } 215 216 /** 217 * @return how many factors {@param newRotation} is rotated 90 degrees clockwise. 218 * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise... 219 * A value of 0 means no rotation has been applied 220 */ deltaRotation(int oldRotation, int newRotation)221 public static int deltaRotation(int oldRotation, int newRotation) { 222 int delta = newRotation - oldRotation; 223 if (delta < 0) delta += 4; 224 return delta; 225 } 226 227 @Override toString()228 public String toString() { 229 return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, " 230 + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, " 231 + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b]", 232 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags, 233 mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting); 234 } 235 } 236