• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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