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.Utilities.dpiFromPx;
24 import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
25 
26 import android.content.SharedPreferences;
27 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28 
29 import com.android.launcher3.BaseActivity;
30 import com.android.launcher3.DeviceProfile;
31 import com.android.launcher3.Utilities;
32 import com.android.launcher3.util.UiThreadHelper;
33 
34 /**
35  * Utility class to manage launcher rotation
36  */
37 public class RotationHelper implements OnSharedPreferenceChangeListener,
38         DeviceProfile.OnDeviceProfileChangeListener {
39 
40     private static final String TAG = "RotationHelper";
41 
42     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
43 
44     /**
45      * Returns the default value of {@link #ALLOW_ROTATION_PREFERENCE_KEY} preference.
46      */
getAllowRotationDefaultValue(DeviceProfile deviceProfile)47     public static boolean getAllowRotationDefaultValue(DeviceProfile deviceProfile) {
48         // If the device's pixel density was scaled (usually via settings for A11y), use the
49         // original dimensions to determine if rotation is allowed of not.
50         float originalSmallestWidth = dpiFromPx(
51                 Math.min(deviceProfile.widthPx, deviceProfile.heightPx), DENSITY_DEVICE_STABLE);
52         return originalSmallestWidth >= MIN_TABLET_WIDTH;
53     }
54 
55     public static final int REQUEST_NONE = 0;
56     public static final int REQUEST_ROTATE = 1;
57     public static final int REQUEST_LOCK = 2;
58 
59     private BaseActivity mActivity;
60     private SharedPreferences mSharedPrefs = null;
61 
62     private boolean mIgnoreAutoRotateSettings;
63     private boolean mForceAllowRotationForTesting;
64     private boolean mHomeRotationEnabled;
65 
66     /**
67      * Rotation request made by
68      * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}.
69      * This supersedes any other request.
70      */
71     private int mStateHandlerRequest = REQUEST_NONE;
72     /**
73      * Rotation request made by an app transition
74      */
75     private int mCurrentTransitionRequest = REQUEST_NONE;
76     /**
77      * Rotation request made by a Launcher State
78      */
79     private int mCurrentStateRequest = REQUEST_NONE;
80 
81     // This is used to defer setting rotation flags until the activity is being created
82     private boolean mInitialized;
83     private boolean mDestroyed;
84 
85     // Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags
86     private int mLastActivityFlags = -999;
87 
RotationHelper(BaseActivity activity)88     public RotationHelper(BaseActivity activity) {
89         mActivity = activity;
90     }
91 
setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings)92     private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings) {
93         // On large devices we do not handle auto-rotate differently.
94         mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
95         if (!mIgnoreAutoRotateSettings) {
96             if (mSharedPrefs == null) {
97                 mSharedPrefs = Utilities.getPrefs(mActivity);
98                 mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
99             }
100             mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
101                     getAllowRotationDefaultValue(mActivity.getDeviceProfile()));
102         } else {
103             if (mSharedPrefs != null) {
104                 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
105                 mSharedPrefs = null;
106             }
107         }
108     }
109 
110     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s)111     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
112         if (mDestroyed) return;
113         boolean wasRotationEnabled = mHomeRotationEnabled;
114         mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
115                 getAllowRotationDefaultValue(mActivity.getDeviceProfile()));
116         if (mHomeRotationEnabled != wasRotationEnabled) {
117             notifyChange();
118         }
119     }
120 
121     @Override
onDeviceProfileChanged(DeviceProfile dp)122     public void onDeviceProfileChanged(DeviceProfile dp) {
123         boolean ignoreAutoRotateSettings = dp.isTablet;
124         if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
125             setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
126             notifyChange();
127         }
128     }
129 
setStateHandlerRequest(int request)130     public void setStateHandlerRequest(int request) {
131         if (mStateHandlerRequest != request) {
132             mStateHandlerRequest = request;
133             notifyChange();
134         }
135     }
136 
setCurrentTransitionRequest(int request)137     public void setCurrentTransitionRequest(int request) {
138         if (mCurrentTransitionRequest != request) {
139             mCurrentTransitionRequest = request;
140             notifyChange();
141         }
142     }
143 
setCurrentStateRequest(int request)144     public void setCurrentStateRequest(int request) {
145         if (mCurrentStateRequest != request) {
146             mCurrentStateRequest = request;
147             notifyChange();
148         }
149     }
150 
151     // Used by tests only.
forceAllowRotationForTesting(boolean allowRotation)152     public void forceAllowRotationForTesting(boolean allowRotation) {
153         mForceAllowRotationForTesting = allowRotation;
154         notifyChange();
155     }
156 
initialize()157     public void initialize() {
158         if (!mInitialized) {
159             mInitialized = true;
160             setIgnoreAutoRotateSettings(mActivity.getDeviceProfile().isTablet);
161             mActivity.addOnDeviceProfileChangeListener(this);
162             notifyChange();
163         }
164     }
165 
destroy()166     public void destroy() {
167         if (!mDestroyed) {
168             mDestroyed = true;
169             mActivity.removeOnDeviceProfileChangeListener(this);
170             mActivity = null;
171             if (mSharedPrefs != null) {
172                 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
173             }
174         }
175     }
176 
notifyChange()177     private void notifyChange() {
178         if (!mInitialized || mDestroyed) {
179             return;
180         }
181 
182         final int activityFlags;
183         if (mStateHandlerRequest != REQUEST_NONE) {
184             activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
185                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
186         } else if (mCurrentTransitionRequest != REQUEST_NONE) {
187             activityFlags = mCurrentTransitionRequest == REQUEST_LOCK ?
188                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
189         } else if (mCurrentStateRequest == REQUEST_LOCK) {
190             activityFlags = SCREEN_ORIENTATION_LOCKED;
191         } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
192                 || mHomeRotationEnabled || mForceAllowRotationForTesting) {
193             activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
194         } else {
195             // If auto rotation is off, allow rotation on the activity, in case the user is using
196             // forced rotation.
197             activityFlags = SCREEN_ORIENTATION_NOSENSOR;
198         }
199         if (activityFlags != mLastActivityFlags) {
200             mLastActivityFlags = activityFlags;
201             UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
202         }
203     }
204 
205     /**
206      * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
207      * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
208      * A value of 0 means no rotation has been applied
209      */
deltaRotation(int oldRotation, int newRotation)210     public static int deltaRotation(int oldRotation, int newRotation) {
211         int delta = newRotation - oldRotation;
212         if (delta < 0) delta += 4;
213         return delta;
214     }
215 
216     @Override
toString()217     public String toString() {
218         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, "
219                         + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, "
220                         + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b]",
221                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
222                 mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting);
223     }
224 }
225