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