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 android.content.SharedPreferences;
24 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
25 import android.content.res.Resources;
26 import android.view.WindowManager;
27 import android.view.WindowManager.LayoutParams;
28 
29 import com.android.launcher3.Launcher;
30 import com.android.launcher3.R;
31 import com.android.launcher3.Utilities;
32 import com.android.launcher3.config.FeatureFlags;
33 import com.android.launcher3.util.UiThreadHelper;
34 
35 /**
36  * Utility class to manage launcher rotation
37  */
38 public class RotationHelper implements OnSharedPreferenceChangeListener {
39 
40     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
41 
getAllowRotationDefaultValue()42     public static boolean getAllowRotationDefaultValue() {
43         // If the device was scaled, used the original dimensions to determine if rotation
44         // is allowed of not.
45         Resources res = Resources.getSystem();
46         int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
47                 * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
48         return originalSmallestWidth >= 600;
49     }
50 
51     public static final int REQUEST_NONE = 0;
52     public static final int REQUEST_ROTATE = 1;
53     public static final int REQUEST_LOCK = 2;
54 
55     private final Launcher mLauncher;
56     private final SharedPreferences mPrefs;
57 
58     private boolean mIgnoreAutoRotateSettings;
59     private boolean mAutoRotateEnabled;
60 
61     /**
62      * Rotation request made by {@link InternalStateHandler}. This supersedes any other request.
63      */
64     private int mStateHandlerRequest = REQUEST_NONE;
65     /**
66      * Rotation request made by an app transition
67      */
68     private int mCurrentTransitionRequest = REQUEST_NONE;
69     /**
70      * Rotation request made by a Launcher State
71      */
72     private int mCurrentStateRequest = REQUEST_NONE;
73 
74     // This is used to defer setting rotation flags until the activity is being created
75     private boolean mInitialized;
76     private boolean mDestroyed;
77     private boolean mRotationHasDifferentUI;
78 
79     private int mLastActivityFlags = -1;
80 
RotationHelper(Launcher launcher)81     public RotationHelper(Launcher launcher) {
82         mLauncher = launcher;
83 
84         // On large devices we do not handle auto-rotate differently.
85         mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
86         if (!mIgnoreAutoRotateSettings) {
87             mPrefs = Utilities.getPrefs(mLauncher);
88             mPrefs.registerOnSharedPreferenceChangeListener(this);
89             mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
90                     getAllowRotationDefaultValue());
91         } else {
92             mPrefs = null;
93         }
94     }
95 
setRotationHadDifferentUI(boolean rotationHasDifferentUI)96     public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) {
97         mRotationHasDifferentUI = rotationHasDifferentUI;
98     }
99 
homeScreenCanRotate()100     public boolean homeScreenCanRotate() {
101         return mRotationHasDifferentUI || mIgnoreAutoRotateSettings || mAutoRotateEnabled
102                 || mStateHandlerRequest != REQUEST_NONE
103                 || mLauncher.getDeviceProfile().isMultiWindowMode;
104     }
105 
updateRotationAnimation()106     public void updateRotationAnimation() {
107         if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
108             WindowManager.LayoutParams lp = mLauncher.getWindow().getAttributes();
109             int oldAnim = lp.rotationAnimation;
110             lp.rotationAnimation = homeScreenCanRotate()
111                     ? WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE
112                     : WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
113             if (oldAnim != lp.rotationAnimation) {
114                 mLauncher.getWindow().setAttributes(lp);
115             }
116         }
117     }
118 
119     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s)120     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
121         boolean wasRotationEnabled = mAutoRotateEnabled;
122         mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
123                 getAllowRotationDefaultValue());
124         if (mAutoRotateEnabled != wasRotationEnabled) {
125 
126             notifyChange();
127             updateRotationAnimation();
128             mLauncher.reapplyUi();
129         }
130     }
131 
setStateHandlerRequest(int request)132     public void setStateHandlerRequest(int request) {
133         if (mStateHandlerRequest != request) {
134             mStateHandlerRequest = request;
135             updateRotationAnimation();
136             notifyChange();
137         }
138     }
139 
setCurrentTransitionRequest(int request)140     public void setCurrentTransitionRequest(int request) {
141         if (mCurrentTransitionRequest != request) {
142             mCurrentTransitionRequest = request;
143             notifyChange();
144         }
145     }
146 
setCurrentStateRequest(int request)147     public void setCurrentStateRequest(int request) {
148         if (mCurrentStateRequest != request) {
149             mCurrentStateRequest = request;
150             notifyChange();
151         }
152     }
153 
154     // Used by tests only.
forceAllowRotationForTesting(boolean allowRotation)155     public void forceAllowRotationForTesting(boolean allowRotation) {
156         mIgnoreAutoRotateSettings =
157                 allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
158         notifyChange();
159     }
160 
initialize()161     public void initialize() {
162         if (!mInitialized) {
163             mInitialized = true;
164             notifyChange();
165             updateRotationAnimation();
166         }
167     }
168 
destroy()169     public void destroy() {
170         if (!mDestroyed) {
171             mDestroyed = true;
172             if (mPrefs != null) {
173                 mPrefs.unregisterOnSharedPreferenceChangeListener(this);
174             }
175         }
176     }
177 
notifyChange()178     private void notifyChange() {
179         if (!mInitialized || mDestroyed) {
180             return;
181         }
182 
183         final int activityFlags;
184         if (mStateHandlerRequest != REQUEST_NONE) {
185             activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
186                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
187         } else if (mCurrentTransitionRequest != REQUEST_NONE) {
188             activityFlags = mCurrentTransitionRequest == REQUEST_LOCK ?
189                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
190         } else if (mCurrentStateRequest == REQUEST_LOCK) {
191             activityFlags = SCREEN_ORIENTATION_LOCKED;
192         } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
193                 || mAutoRotateEnabled) {
194             activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
195         } else {
196             // If auto rotation is off, allow rotation on the activity, in case the user is using
197             // forced rotation.
198             activityFlags = SCREEN_ORIENTATION_NOSENSOR;
199         }
200         if (activityFlags != mLastActivityFlags) {
201             mLastActivityFlags = activityFlags;
202             UiThreadHelper.setOrientationAsync(mLauncher, activityFlags);
203         }
204     }
205 
206     @Override
toString()207     public String toString() {
208         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
209                 + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mAutoRotateEnabled=%b]",
210                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
211                 mIgnoreAutoRotateSettings, mAutoRotateEnabled);
212     }
213 }
214