• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.quickstep.util;
18 
19 import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN;
20 import static android.view.Surface.ROTATION_0;
21 import static android.view.Surface.ROTATION_180;
22 import static android.view.Surface.ROTATION_270;
23 import static android.view.Surface.ROTATION_90;
24 
25 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
26 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
27 import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
28 import static com.android.quickstep.BaseActivityInterface.getTaskDimension;
29 
30 import static java.lang.annotation.RetentionPolicy.SOURCE;
31 
32 import android.content.Context;
33 import android.content.SharedPreferences;
34 import android.graphics.Matrix;
35 import android.graphics.Point;
36 import android.graphics.PointF;
37 import android.graphics.Rect;
38 import android.util.Log;
39 import android.view.MotionEvent;
40 import android.view.OrientationEventListener;
41 import android.view.Surface;
42 
43 import androidx.annotation.IntDef;
44 import androidx.annotation.NonNull;
45 
46 import com.android.launcher3.DeviceProfile;
47 import com.android.launcher3.InvariantDeviceProfile;
48 import com.android.launcher3.Utilities;
49 import com.android.launcher3.testing.TestProtocol;
50 import com.android.launcher3.touch.PagedOrientationHandler;
51 import com.android.launcher3.util.DisplayController;
52 import com.android.launcher3.util.SettingsCache;
53 import com.android.quickstep.BaseActivityInterface;
54 import com.android.quickstep.SystemUiProxy;
55 import com.android.quickstep.views.TaskView;
56 
57 import java.lang.annotation.Retention;
58 import java.util.function.IntConsumer;
59 
60 /**
61  * Container to hold orientation/rotation related information for Launcher.
62  * This is not meant to be an abstraction layer for applying different functionality between
63  * the different orientation/rotations. For that see {@link PagedOrientationHandler}
64  *
65  * This class has initial default state assuming the device and foreground app have
66  * no ({@link Surface#ROTATION_0} rotation.
67  */
68 public class RecentsOrientedState implements
69         SharedPreferences.OnSharedPreferenceChangeListener {
70 
71     private static final String TAG = "RecentsOrientedState";
72     private static final boolean DEBUG = false;
73 
74     @Retention(SOURCE)
75     @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
76     public @interface SurfaceRotation {}
77 
78     private PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
79 
80     private @SurfaceRotation int mTouchRotation = ROTATION_0;
81     private @SurfaceRotation int mDisplayRotation = ROTATION_0;
82     private @SurfaceRotation int mRecentsActivityRotation = ROTATION_0;
83     private @SurfaceRotation int mRecentsRotation = ROTATION_0 - 1;
84 
85     // Launcher activity supports multiple orientation, but fallback activity does not
86     private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
87     // Multiple orientation is only supported if density is < 600
88     private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY = 1 << 1;
89     // Shared prefs for rotation, only if activity supports it
90     private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 2;
91     // If the user has enabled system rotation
92     private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 3;
93     // Multiple orientation is not supported in multiwindow mode
94     private static final int FLAG_MULTIWINDOW_ROTATION_ALLOWED = 1 << 4;
95     // Whether to rotation sensor is supported on the device
96     private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 5;
97     // Whether to enable rotation watcher when multi-rotation is supported
98     private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
99     // Enable home rotation for UI tests, ignoring home rotation value from prefs
100     private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 7;
101     // Whether the swipe gesture is running, so the recents would stay locked in the
102     // current orientation
103     private static final int FLAG_SWIPE_UP_NOT_RUNNING = 1 << 8;
104 
105     private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
106             FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
107             | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
108 
109     // State for which rotation watcher will be enabled. We skip it when home rotation or
110     // multi-window is enabled as in that case, activity itself rotates.
111     private static final int VALUE_ROTATION_WATCHER_ENABLED =
112             MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
113                     | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED
114                     | FLAG_SWIPE_UP_NOT_RUNNING;
115 
116     private final Context mContext;
117     private final SharedPreferences mSharedPrefs;
118     private final OrientationEventListener mOrientationListener;
119     private final SettingsCache mSettingsCache;
120     private final SettingsCache.OnChangeListener mRotationChangeListener =
121             isEnabled -> updateAutoRotateSetting();
122 
123     private final Matrix mTmpMatrix = new Matrix();
124 
125     private int mFlags;
126     private int mPreviousRotation = ROTATION_0;
127     private boolean mListenersInitialized = false;
128 
129     // Combined int which encodes the full state.
130     private int mStateId = 0;
131 
132     /**
133      * @param rotationChangeListener Callback for receiving rotation events when rotation watcher
134      *                              is enabled
135      * @see #setRotationWatcherEnabled(boolean)
136      */
RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy, IntConsumer rotationChangeListener)137     public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
138             IntConsumer rotationChangeListener) {
139         mContext = context;
140         mSharedPrefs = Utilities.getPrefs(context);
141         mOrientationListener = new OrientationEventListener(context) {
142             @Override
143             public void onOrientationChanged(int degrees) {
144                 int newRotation = getRotationForUserDegreesRotated(degrees, mPreviousRotation);
145                 if (newRotation != mPreviousRotation) {
146                     mPreviousRotation = newRotation;
147                     rotationChangeListener.accept(newRotation);
148                 }
149             }
150         };
151 
152         mFlags = sizeStrategy.rotationSupportedByActivity
153                 ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
154 
155         mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
156         mSettingsCache = SettingsCache.INSTANCE.get(mContext);
157         initFlags();
158     }
159 
160     /**
161      * Sets the device profile for the current state.
162      */
setDeviceProfile(DeviceProfile deviceProfile)163     public void setDeviceProfile(DeviceProfile deviceProfile) {
164         boolean oldMultipleOrientationsSupported = isMultipleOrientationSupportedByDevice();
165         setFlag(FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY, !deviceProfile.allowRotation);
166         if (mListenersInitialized) {
167             boolean newMultipleOrientationsSupported = isMultipleOrientationSupportedByDevice();
168             // If isMultipleOrientationSupportedByDevice is changed, init or destroy listeners
169             // accordingly.
170             if (newMultipleOrientationsSupported != oldMultipleOrientationsSupported) {
171                 if (newMultipleOrientationsSupported) {
172                     initMultipleOrientationListeners();
173                 } else {
174                     destroyMultipleOrientationListeners();
175                 }
176             }
177         }
178     }
179 
180     /**
181      * Sets the rotation for the recents activity, which could affect the appearance of task view.
182      * @see #update(int, int)
183      */
setRecentsRotation(@urfaceRotation int recentsRotation)184     public boolean setRecentsRotation(@SurfaceRotation int recentsRotation) {
185         mRecentsRotation = recentsRotation;
186         return updateHandler();
187     }
188 
189     /**
190      * Sets if the host is in multi-window mode
191      */
setMultiWindowMode(boolean isMultiWindow)192     public void setMultiWindowMode(boolean isMultiWindow) {
193         setFlag(FLAG_MULTIWINDOW_ROTATION_ALLOWED, isMultiWindow);
194     }
195 
196     /**
197      * Sets if the swipe up gesture is currently running or not
198      */
setGestureActive(boolean isGestureActive)199     public boolean setGestureActive(boolean isGestureActive) {
200         return setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
201     }
202 
203     /**
204      * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
205      * @param touchRotation The rotation the nav bar region that is touched is in
206      * @param displayRotation Rotation of the display/device
207      *
208      * @return true if there was any change in the internal state as a result of this call,
209      *         false otherwise
210      */
update( @urfaceRotation int touchRotation, @SurfaceRotation int displayRotation)211     public boolean update(
212             @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
213         mDisplayRotation = displayRotation;
214         mTouchRotation = touchRotation;
215         mPreviousRotation = touchRotation;
216         return updateHandler();
217     }
218 
updateHandler()219     private boolean updateHandler() {
220         mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
221         if (mRecentsActivityRotation == mTouchRotation
222                 || (canRecentsActivityRotate() && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) {
223             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
224         } else if (mTouchRotation == ROTATION_90) {
225             mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
226         } else if (mTouchRotation == ROTATION_270) {
227             mOrientationHandler = PagedOrientationHandler.SEASCAPE;
228         } else {
229             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
230         }
231         if (DEBUG) {
232             Log.d(TAG, "current RecentsOrientedState: " + this);
233         }
234 
235         int oldStateId = mStateId;
236         // Each SurfaceRotation value takes two bits
237         mStateId = (((((mFlags << 2)
238                 | mDisplayRotation) << 2)
239                 | mTouchRotation) << 3)
240                 | (mRecentsRotation < 0 ? 7 : mRecentsRotation);
241         return mStateId != oldStateId;
242     }
243 
244     @SurfaceRotation
inferRecentsActivityRotation(@urfaceRotation int displayRotation)245     private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
246         if (isRecentsActivityRotationAllowed()) {
247             return mRecentsRotation < 0 ? displayRotation : mRecentsRotation;
248         } else {
249             return ROTATION_0;
250         }
251     }
252 
setFlag(int mask, boolean enabled)253     private boolean setFlag(int mask, boolean enabled) {
254         boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
255                 && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
256                 && !canRecentsActivityRotate();
257         if (enabled) {
258             mFlags |= mask;
259         } else {
260             mFlags &= ~mask;
261         }
262 
263         boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation
264                 && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
265                 && !canRecentsActivityRotate();
266         if (wasRotationEnabled != isRotationEnabled) {
267             UI_HELPER_EXECUTOR.execute(() -> {
268                 if (isRotationEnabled) {
269                     mOrientationListener.enable();
270                 } else {
271                     mOrientationListener.disable();
272                 }
273             });
274         }
275         return updateHandler();
276     }
277 
278     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s)279     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
280         if (ALLOW_ROTATION_PREFERENCE_KEY.equals(s)) {
281             updateHomeRotationSetting();
282         }
283     }
284 
updateAutoRotateSetting()285     private void updateAutoRotateSetting() {
286         setFlag(FLAG_SYSTEM_ROTATION_ALLOWED,
287                 mSettingsCache.getValue(ROTATION_SETTING_URI, 1));
288     }
289 
updateHomeRotationSetting()290     private void updateHomeRotationSetting() {
291         boolean homeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false);
292         setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS, homeRotationEnabled);
293         SystemUiProxy.INSTANCE.get(mContext).setHomeRotationEnabled(homeRotationEnabled);
294     }
295 
initFlags()296     private void initFlags() {
297         setFlag(FLAG_ROTATION_WATCHER_SUPPORTED, mOrientationListener.canDetectOrientation());
298 
299         // initialize external flags
300         updateAutoRotateSetting();
301         updateHomeRotationSetting();
302     }
303 
initMultipleOrientationListeners()304     private void initMultipleOrientationListeners() {
305         mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
306         mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
307     }
308 
destroyMultipleOrientationListeners()309     private void destroyMultipleOrientationListeners() {
310         mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
311         mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener);
312     }
313 
314     /**
315      * Initializes any system values and registers corresponding change listeners. It must be
316      * paired with {@link #destroyListeners()} call
317      */
initListeners()318     public void initListeners() {
319         mListenersInitialized = true;
320         if (isMultipleOrientationSupportedByDevice()) {
321             initMultipleOrientationListeners();
322         }
323         initFlags();
324     }
325 
326     /**
327      * Unregisters any previously registered listeners.
328      */
destroyListeners()329     public void destroyListeners() {
330         mListenersInitialized = false;
331         if (isMultipleOrientationSupportedByDevice()) {
332             destroyMultipleOrientationListeners();
333         }
334         setRotationWatcherEnabled(false);
335     }
336 
forceAllowRotationForTesting(boolean forceAllow)337     public void forceAllowRotationForTesting(boolean forceAllow) {
338         setFlag(FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING, forceAllow);
339     }
340 
341     @SurfaceRotation
getDisplayRotation()342     public int getDisplayRotation() {
343         return mDisplayRotation;
344     }
345 
346     @SurfaceRotation
getTouchRotation()347     public int getTouchRotation() {
348         return mTouchRotation;
349     }
350 
351     @SurfaceRotation
getRecentsActivityRotation()352     public int getRecentsActivityRotation() {
353         return mRecentsActivityRotation;
354     }
355 
356     /**
357      * Returns an id that can be used to tracking internal changes
358      */
getStateId()359     public int getStateId() {
360         return mStateId;
361     }
362 
isMultipleOrientationSupportedByDevice()363     public boolean isMultipleOrientationSupportedByDevice() {
364         return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
365                 == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE;
366     }
367 
isRecentsActivityRotationAllowed()368     public boolean isRecentsActivityRotationAllowed() {
369         // Activity rotation is allowed if the multi-simulated-rotation is not supported
370         // (fallback recents or tablets) or activity rotation is enabled by various settings.
371         return ((mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
372                 != MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
373                 || (mFlags & (FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
374                         | FLAG_MULTIWINDOW_ROTATION_ALLOWED
375                         | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
376     }
377 
378     /**
379      * Returns true if the activity can rotate, if allowed by system rotation settings
380      */
canRecentsActivityRotate()381     public boolean canRecentsActivityRotate() {
382         return (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0 && isRecentsActivityRotationAllowed();
383     }
384 
385     /**
386      * Enables or disables the rotation watcher for listening to rotation callbacks
387      */
setRotationWatcherEnabled(boolean isEnabled)388     public void setRotationWatcherEnabled(boolean isEnabled) {
389         setFlag(FLAG_ROTATION_WATCHER_ENABLED, isEnabled);
390     }
391 
392     /**
393      * Returns the scale and pivot so that the provided taskRect can fit the provided full size
394      */
getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot)395     public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
396         Rect insets = dp.getInsets();
397         float fullWidth = dp.widthPx;
398         float fullHeight = dp.heightPx;
399         if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
400             fullWidth -= insets.left + insets.right;
401             fullHeight -= insets.top + insets.bottom;
402         }
403 
404         getTaskDimension(mContext, dp, outPivot);
405         float scale = Math.min(outPivot.x / taskView.width(), outPivot.y / taskView.height());
406         // We also scale the preview as part of fullScreenParams, so account for that as well.
407         if (fullWidth > 0) {
408             scale = scale * dp.widthPx / fullWidth;
409         }
410 
411         if (scale == 1) {
412             outPivot.set(fullWidth / 2, fullHeight / 2);
413         } else if (dp.isMultiWindowMode) {
414             float denominator = 1 / (scale - 1);
415             // Ensure that the task aligns to right bottom for the root view
416             float y = (scale * taskView.bottom - fullHeight) * denominator;
417             float x = (scale * taskView.right - fullWidth) * denominator;
418             outPivot.set(x, y);
419         } else {
420             float factor = scale / (scale - 1);
421             outPivot.set(taskView.left * factor, taskView.top * factor);
422         }
423         return scale;
424     }
425 
getOrientationHandler()426     public PagedOrientationHandler getOrientationHandler() {
427         return mOrientationHandler;
428     }
429 
430     /**
431      * For landscape, since the navbar is already in a vertical position, we don't have to do any
432      * rotations as the change in Y coordinate is what is read. We only flip the sign of the
433      * y coordinate to make it match existing behavior of swipe to the top to go previous
434      */
flipVertical(MotionEvent ev)435     public void flipVertical(MotionEvent ev) {
436         mTmpMatrix.setScale(1, -1);
437         ev.transform(mTmpMatrix);
438     }
439 
440     /**
441      * Creates a matrix to transform the given motion event specified by degrees.
442      * If inverse is {@code true}, the inverse of that matrix will be applied
443      */
transformEvent(float degrees, MotionEvent ev, boolean inverse)444     public void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
445         mTmpMatrix.setRotate(inverse ? -degrees : degrees);
446         ev.transform(mTmpMatrix);
447 
448         // TODO: Add scaling back in based on degrees
449         /*
450         if (getWidth() > 0 && getHeight() > 0) {
451             float scale = ((float) getWidth()) / getHeight();
452             transform.postScale(scale, 1 / scale);
453         }
454         */
455     }
456 
457     @SurfaceRotation
getRotationForUserDegreesRotated(float degrees, int currentRotation)458     public static int getRotationForUserDegreesRotated(float degrees, int currentRotation) {
459         if (degrees == ORIENTATION_UNKNOWN) {
460             return currentRotation;
461         }
462 
463         int threshold = 70;
464         switch (currentRotation) {
465             case ROTATION_0:
466                 if (degrees > 180 && degrees < (360 - threshold)) {
467                     return ROTATION_90;
468                 }
469                 if (degrees < 180 && degrees > threshold) {
470                     return ROTATION_270;
471                 }
472                 break;
473             case ROTATION_270:
474                 if (degrees < (90 - threshold) ||
475                         (degrees > (270 + threshold) && degrees < 360)) {
476                     return ROTATION_0;
477                 }
478                 if (degrees > (90 + threshold) && degrees < 180) {
479                     return ROTATION_180;
480                 }
481                 // flip from seascape to landscape
482                 if (degrees > (180 + threshold) && degrees < 360) {
483                     return ROTATION_90;
484                 }
485                 break;
486             case ROTATION_180:
487                 if (degrees < (180 - threshold)) {
488                     return ROTATION_270;
489                 }
490                 if (degrees > (180 + threshold)) {
491                     return ROTATION_90;
492                 }
493                 break;
494             case ROTATION_90:
495                 if (degrees < (270 - threshold) && degrees > 90) {
496                     return ROTATION_180;
497                 }
498                 if (degrees > (270 + threshold) && degrees < 360
499                         || (degrees >= 0 && degrees < threshold)) {
500                     return ROTATION_0;
501                 }
502                 // flip from landscape to seascape
503                 if (degrees > threshold && degrees < 180) {
504                     return ROTATION_270;
505                 }
506                 break;
507         }
508 
509         return currentRotation;
510     }
511 
isDisplayPhoneNatural()512     public boolean isDisplayPhoneNatural() {
513         return mDisplayRotation == Surface.ROTATION_0 || mDisplayRotation == Surface.ROTATION_180;
514     }
515 
516     /**
517      * Posts the transformation on the matrix representing the provided display rotation
518      */
postDisplayRotation(@urfaceRotation int displayRotation, float screenWidth, float screenHeight, Matrix out)519     public static void postDisplayRotation(@SurfaceRotation int displayRotation,
520             float screenWidth, float screenHeight, Matrix out) {
521         switch (displayRotation) {
522             case ROTATION_0:
523                 return;
524             case ROTATION_90:
525                 out.postRotate(270);
526                 out.postTranslate(0, screenWidth);
527                 break;
528             case ROTATION_180:
529                 out.postRotate(180);
530                 out.postTranslate(screenHeight, screenWidth);
531                 break;
532             case ROTATION_270:
533                 out.postRotate(90);
534                 out.postTranslate(screenHeight, 0);
535                 break;
536         }
537     }
538 
539     /**
540      * Contrary to {@link #postDisplayRotation}.
541      */
preDisplayRotation(@urfaceRotation int displayRotation, float screenWidth, float screenHeight, Matrix out)542     public static void preDisplayRotation(@SurfaceRotation int displayRotation,
543             float screenWidth, float screenHeight, Matrix out) {
544         switch (displayRotation) {
545             case ROTATION_0:
546                 return;
547             case ROTATION_90:
548                 out.postRotate(90);
549                 out.postTranslate(screenWidth, 0);
550                 break;
551             case ROTATION_180:
552                 out.postRotate(180);
553                 out.postTranslate(screenHeight, screenWidth);
554                 break;
555             case ROTATION_270:
556                 out.postRotate(270);
557                 out.postTranslate(0, screenHeight);
558                 break;
559         }
560     }
561 
562     @NonNull
563     @Override
toString()564     public String toString() {
565         boolean systemRotationOn = (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0;
566         return "["
567                 + "this=" + nameAndAddress(this)
568                 + " mOrientationHandler=" + nameAndAddress(mOrientationHandler)
569                 + " mDisplayRotation=" + mDisplayRotation
570                 + " mTouchRotation=" + mTouchRotation
571                 + " mRecentsActivityRotation=" + mRecentsActivityRotation
572                 + " mRecentsRotation=" + mRecentsRotation
573                 + " isRecentsActivityRotationAllowed=" + isRecentsActivityRotationAllowed()
574                 + " mSystemRotation=" + systemRotationOn
575                 + " mStateId=" + mStateId
576                 + " mFlags=" + mFlags
577                 + "]";
578     }
579 
580     /**
581      * Returns the device profile based on expected launcher rotation
582      */
getLauncherDeviceProfile()583     public DeviceProfile getLauncherDeviceProfile() {
584         InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
585         Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
586 
587         int width, height;
588         if ((mRecentsActivityRotation == ROTATION_90 || mRecentsActivityRotation == ROTATION_270)) {
589             width = Math.max(currentSize.x, currentSize.y);
590             height = Math.min(currentSize.x, currentSize.y);
591         } else {
592             width = Math.min(currentSize.x, currentSize.y);
593             height = Math.max(currentSize.x, currentSize.y);
594         }
595 
596         DeviceProfile bestMatch = idp.supportedProfiles.get(0);
597         float minDiff = Float.MAX_VALUE;
598         for (DeviceProfile profile : idp.supportedProfiles) {
599             float diff = Math.abs(profile.widthPx - width) + Math.abs(profile.heightPx - height);
600             if (diff < minDiff) {
601                 minDiff = diff;
602                 bestMatch = profile;
603             }
604         }
605         return bestMatch;
606     }
607 
nameAndAddress(Object obj)608     private static String nameAndAddress(Object obj) {
609         return obj.getClass().getSimpleName() + "@" + obj.hashCode();
610     }
611 }
612