From 98dd5315aa9022fb4bb9d72321c41c377742e47a Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 12 May 2021 23:19:51 -0400 Subject: [PATCH] Rotation-invariant status bar layout calculator This CL implements a handful of methods that enable us to pre-emptively calculate the position of the status bar, taking into account DisplayCutout and rounded corner content padding. The status bar is very special in that it always sits at the top of the device, and has a known height, only taking into account window insets with regard to its content layout. Therefore it can be known in all orientations what the positions will be before we get a chance to lay out the views on a configuration change. Still to do: use this to inform Window Manager APIs, and use this information to rework the privacy dot view controller not to require a late publication of the status bar location Test: manual Bug: 187973222 Change-Id: Ieeba175065c522a0616f109b3670b752faef7f2a --- .../drawable/system_animation_ongoing_dot.xml | 4 +- .../res/layout/ongoing_privacy_chip.xml | 3 +- packages/SystemUI/res/values/dimens.xml | 5 + .../phone/CollapsedStatusBarFragment.java | 2 - .../phone/StatusBarContentInsetsProvider.kt | 403 ++++++++++++++++++ .../phone/StatusBarWindowController.java | 39 +- .../systemui/util/leak/RotationUtils.java | 78 +++- 7 files changed, 526 insertions(+), 8 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt diff --git a/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml index 9f81b0d59973f..09af6d2dace7e 100644 --- a/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml +++ b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml @@ -20,7 +20,7 @@ \ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml index 676e49298cefe..812277634d094 100644 --- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml @@ -32,6 +32,7 @@ android:paddingEnd="10dp" android:gravity="center" android:layout_gravity="center" - android:minWidth="56dp" + android:minWidth="@dimen/ongoing_appops_chip_min_width" + android:maxWidth="@dimen/ongoing_appops_chip_max_width" /> \ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 915b62791f585..7b821eb7e2bbe 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1239,6 +1239,11 @@ 16dp 28dp + + 56dp + + 76dp + 6dp @dimen/notification_shade_content_margin_horizontal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 684760e6c3a3d..69360b290118a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -30,7 +30,6 @@ import android.annotation.Nullable; import android.app.Fragment; import android.os.Bundle; import android.os.Parcelable; -import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; @@ -230,7 +229,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if (displayId != getContext().getDisplayId()) { return; } - Log.d(TAG, "disable: "); state1 = adjustDisableFlags(state1); final int old1 = mDisabled1; final int diff1 = state1 ^ old1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt new file mode 100644 index 0000000000000..8befe800b4e1d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.util.Pair +import android.view.DisplayCutout +import android.view.View.LAYOUT_DIRECTION_RTL +import android.view.WindowManager +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.leak.RotationUtils +import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE +import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE +import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE +import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN +import com.android.systemui.util.leak.RotationUtils.Rotation +import java.io.FileDescriptor +import java.io.PrintWriter +import java.lang.Math.max +import javax.inject.Inject + +/** + * Encapsulates logic that can solve for the left/right insets required for the status bar contents. + * Takes into account: + * 1. rounded_corner_content_padding + * 2. status_bar_padding_start, status_bar_padding_end + * 2. display cutout insets from left or right + * 3. waterfall insets + * + * + * Importantly, these functions can determine status bar content left/right insets for any rotation + * before having done a layout pass in that rotation. + * + * NOTE: This class is not threadsafe + */ +@SysUISingleton +class StatusBarContentInsetsProvider @Inject constructor( + val context: Context, + val configurationController: ConfigurationController, + val windowManager: WindowManager, + val dumpManager: DumpManager +) : CallbackController, + ConfigurationController.ConfigurationListener, + Dumpable { + // Indexed by @Rotation + private val insetsByCorner = arrayOfNulls(4) + private val listeners = mutableSetOf() + + init { + configurationController.addCallback(this) + dumpManager.registerDumpable(TAG, this) + } + + override fun addCallback(listener: StatusBarContentInsetsChangedListener) { + listeners.add(listener) + } + + override fun removeCallback(listener: StatusBarContentInsetsChangedListener) { + listeners.remove(listener) + } + + override fun onDensityOrFontScaleChanged() { + clearCachedInsets() + } + + override fun onOverlayChanged() { + clearCachedInsets() + } + + private fun clearCachedInsets() { + insetsByCorner[0] = null + insetsByCorner[1] = null + insetsByCorner[2] = null + insetsByCorner[3] = null + + notifyInsetsChanged() + } + + private fun notifyInsetsChanged() { + listeners.forEach { + it.onStatusBarContentInsetsChanged() + } + } + + /** + * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy + * dot in the coordinates relative to the given rotation. + */ + fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int): Rect { + var insets = insetsByCorner[rotation] + val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context) + if (insets == null) { + insets = getAndSetInsetsForRotation(rotation, rotatedResources) + } + + val dotWidth = rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter) + val chipWidth = rotatedResources.getDimensionPixelSize( + R.dimen.ongoing_appops_chip_max_width) + + return if (context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) { + Rect(insets.left - dotWidth, + insets.top, + insets.left + chipWidth, + insets.bottom) + } else { + Rect(insets.right - chipWidth, + insets.top, + insets.right + dotWidth, + insets.bottom) + } + } + + /** + * Calculates the necessary left and right locations for the status bar contents invariant of + * the current device rotation, in the target rotation's coordinates + */ + fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Rect { + var insets = insetsByCorner[rotation] + if (insets == null) { + val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context) + insets = getCalculatedInsetsForRotation(rotation, rotatedResources) + insetsByCorner[rotation] = insets + } + + return insets + } + + private fun getAndSetInsetsForRotation( + @Rotation rot: Int, + rotatedResources: Resources + ): Rect { + val insets = getCalculatedInsetsForRotation(rot, rotatedResources) + insetsByCorner[rot] = insets + + return insets + } + + private fun getCalculatedInsetsForRotation( + @Rotation rotation: Int, + rotatedResources: Resources + ): Rect { + val dc = context.display.cutout + + return calculateInsetsForRotationWithRotatedResources( + rotation, rotatedResources, dc, windowManager, context) + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { + insetsByCorner.forEachIndexed { index, rect -> + pw.println("${RotationUtils.toString(index)} -> $rect") + } + } +} + +interface StatusBarContentInsetsChangedListener { + fun onStatusBarContentInsetsChanged() +} + +private const val TAG = "StatusBarInsetsProvider" + +private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotation: Int): Rect { + val bounds = wm.maximumWindowMetrics.bounds + + if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) { + return bounds + } + + // bounds are horizontal, swap height and width + return Rect(0, 0, bounds.bottom, bounds.right) +} + +private fun getCurrentDisplayBounds(wm: WindowManager): Rect { + val bounds = wm.maximumWindowMetrics.bounds + return bounds +} + +/** + * Calculates the exact left and right positions for the status bar contents for the given + * rotation + * + * @param rot rotation for which to query the margins + * @param context systemui context + * @param rotatedResources resources constructed with the proper orientation set + * + * @see [RotationUtils#getResourcesForRotation] + */ +fun calculateInsetsForRotationWithRotatedResources( + @Rotation targetRotation: Int, + rotatedResources: Resources, + displayCutout: DisplayCutout?, + windowmanager: WindowManager, + context: Context +): Rect { + val rtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL + + val exactRotation = RotationUtils.getExactRotation(context) + val height = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height) + + /* + TODO: Check if this is ever used for devices with no rounded corners + val paddingStart = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_start) + val paddingEnd = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_end) + val left = if (rtl) paddingEnd else paddingStart + val right = if(rtl) paddingStart else paddingEnd + */ + + val roundedCornerPadding = rotatedResources.getDimensionPixelSize( + R.dimen.rounded_corner_content_padding) + + val rotZeroBounds = getRotationZeroDisplayBounds(windowmanager, exactRotation) + val currentBounds = getCurrentDisplayBounds(windowmanager) + + val sbLeftRight = getStatusBarLeftRight( + displayCutout, + height, + rotZeroBounds.right, + rotZeroBounds.bottom, + currentBounds.width(), + currentBounds.height(), + roundedCornerPadding, + targetRotation, + exactRotation) + + return sbLeftRight +} + +/** + * Calculate the insets needed from the left and right edges for the given rotation. + * + * @param dc Device display cutout + * @param sbHeight appropriate status bar height for this rotation + * @param width display width calculated for ROTATION_NONE + * @param height display height calculated for ROTATION_NONE + * @param roundedCornerPadding rounded_corner_content_padding dimension + * @param targetRotation the rotation for which to calculate margins + * @param currentRotation the rotation from which the display cutout was generated + * + * @return a Rect which exactly calculates the Status Bar's content rect relative to the target + * rotation + */ +private fun getStatusBarLeftRight( + dc: DisplayCutout?, + sbHeight: Int, + width: Int, + height: Int, + cWidth: Int, + cHeight: Int, + roundedCornerPadding: Int, + @Rotation targetRotation: Int, + @Rotation currentRotation: Int +): Rect { + + val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width + + val cutoutRects = dc?.boundingRects + if (cutoutRects == null || cutoutRects.isEmpty()) { + return Rect(roundedCornerPadding, + 0, + logicalDisplayWidth - roundedCornerPadding, + sbHeight) + } + + val relativeRotation = if (currentRotation - targetRotation < 0) { + currentRotation - targetRotation + 4 + } else { + currentRotation - targetRotation + } + + // Size of the status bar window for the given rotation relative to our exact rotation + val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight)) + + var leftMargin = roundedCornerPadding + var rightMargin = roundedCornerPadding + for (cutoutRect in cutoutRects) { + // There is at most one non-functional area per short edge of the device. So if the status + // bar doesn't share a short edge with the cutout, we can ignore its insets because there + // will be no letter-boxing to worry about + if (!shareShortEdge(sbRect, cutoutRect, cWidth, cHeight)) { + continue + } + + if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) { + + val l = max(roundedCornerPadding, cutoutRect.logicalWidth(relativeRotation)) + leftMargin = max(l, leftMargin) + } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) { + val logicalWidth = cutoutRect.logicalWidth(relativeRotation) + rightMargin = max(roundedCornerPadding, logicalWidth) + } + } + + return Rect(leftMargin, 0, logicalDisplayWidth - rightMargin, sbHeight) +} + +private fun sbRect( + @Rotation relativeRotation: Int, + sbHeight: Int, + displaySize: Pair +): Rect { + val w = displaySize.first + val h = displaySize.second + return when (relativeRotation) { + ROTATION_NONE -> Rect(0, 0, w, sbHeight) + ROTATION_LANDSCAPE -> Rect(0, 0, sbHeight, h) + ROTATION_UPSIDE_DOWN -> Rect(0, h - sbHeight, w, h) + else -> Rect(w - sbHeight, 0, w, h) + } +} + +private fun shareShortEdge( + sbRect: Rect, + cutoutRect: Rect, + currentWidth: Int, + currentHeight: Int +): Boolean { + if (currentWidth < currentHeight) { + // Check top/bottom edges by extending the width of the display cutout rect and checking + // for intersections + return sbRect.intersects(0, cutoutRect.top, currentWidth, cutoutRect.bottom) + } else if (currentWidth > currentHeight) { + // Short edge is the height, extend that one this time + return sbRect.intersects(cutoutRect.left, 0, cutoutRect.right, currentHeight) + } + + return false +} + +private fun Rect.touchesRightEdge(@Rotation rot: Int, width: Int, height: Int): Boolean { + return when (rot) { + ROTATION_NONE -> right >= width + ROTATION_LANDSCAPE -> top <= 0 + ROTATION_UPSIDE_DOWN -> left <= 0 + else /* SEASCAPE */ -> bottom >= height + } +} + +private fun Rect.touchesLeftEdge(@Rotation rot: Int, width: Int, height: Int): Boolean { + return when (rot) { + ROTATION_NONE -> left <= 0 + ROTATION_LANDSCAPE -> bottom >= height + ROTATION_UPSIDE_DOWN -> right >= width + else /* SEASCAPE */ -> top <= 0 + } +} + +private fun Rect.logicalTop(@Rotation rot: Int): Int { + return when (rot) { + ROTATION_NONE -> top + ROTATION_LANDSCAPE -> left + ROTATION_UPSIDE_DOWN -> bottom + else /* SEASCAPE */ -> right + } +} + +private fun Rect.logicalRight(@Rotation rot: Int): Int { + return when (rot) { + ROTATION_NONE -> right + ROTATION_LANDSCAPE -> top + ROTATION_UPSIDE_DOWN -> left + else /* SEASCAPE */ -> bottom + } +} + +private fun Rect.logicalLeft(@Rotation rot: Int): Int { + return when (rot) { + ROTATION_NONE -> left + ROTATION_LANDSCAPE -> bottom + ROTATION_UPSIDE_DOWN -> right + else /* SEASCAPE */ -> top + } +} + +private fun Rect.logicalWidth(@Rotation rot: Int): Int { + return when (rot) { + ROTATION_NONE, ROTATION_UPSIDE_DOWN -> width() + else /* LANDSCAPE, SEASCAPE */ -> height() + } +} + +private fun Int.isHorizontal(): Boolean { + return this == ROTATION_LANDSCAPE || this == ROTATION_SEASCAPE +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 30b8c5c0d8d34..9a25a70788592 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -20,12 +20,20 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN; + import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.os.Binder; +import android.os.RemoteException; import android.util.Log; import android.view.Gravity; +import android.view.IWindowManager; import android.view.ViewGroup; import android.view.WindowManager; @@ -46,7 +54,9 @@ public class StatusBarWindowController { private final Context mContext; private final WindowManager mWindowManager; + private final IWindowManager mIWindowManager; private final SuperStatusBarViewFactory mSuperStatusBarViewFactory; + private final StatusBarContentInsetsProvider mContentInsetsProvider; private final Resources mResources; private int mBarHeight = -1; private final State mCurrentState = new State(); @@ -57,11 +67,17 @@ public class StatusBarWindowController { private final WindowManager.LayoutParams mLpChanged; @Inject - public StatusBarWindowController(Context context, WindowManager windowManager, + public StatusBarWindowController( + Context context, + WindowManager windowManager, + IWindowManager iWindowManager, SuperStatusBarViewFactory superStatusBarViewFactory, + StatusBarContentInsetsProvider contentInsetsProvider, @Main Resources resources) { mContext = context; mWindowManager = windowManager; + mIWindowManager = iWindowManager; + mContentInsetsProvider = contentInsetsProvider; mSuperStatusBarViewFactory = superStatusBarViewFactory; mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView(); mLaunchAnimationContainer = mStatusBarView.findViewById( @@ -120,6 +136,27 @@ public class StatusBarWindowController { mWindowManager.addView(mStatusBarView, mLp); mLpChanged.copyFrom(mLp); + + mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations); + calculateStatusBarLocationsForAllRotations(); + } + + private void calculateStatusBarLocationsForAllRotations() { + Rect[] bounds = new Rect[4]; + bounds[0] = mContentInsetsProvider + .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE); + bounds[1] = mContentInsetsProvider + .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE); + bounds[2] = mContentInsetsProvider + .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN); + bounds[3] = mContentInsetsProvider + .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE); + + try { + mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds); + } catch (RemoteException e) { + //Swallow + } } /** Set force status bar visible. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java index e44981e249064..b9b7730c67f3b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java @@ -14,16 +14,40 @@ package com.android.systemui.util.leak; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +import android.annotation.IntDef; import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; import android.view.Surface; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class RotationUtils { public static final int ROTATION_NONE = 0; public static final int ROTATION_LANDSCAPE = 1; - public static final int ROTATION_SEASCAPE = 2; - public static final int ROTATION_UPSIDE_DOWN = 3; + public static final int ROTATION_UPSIDE_DOWN = 2; + public static final int ROTATION_SEASCAPE = 3; + // Not to be confused with Surface.Rotation + @IntDef(prefix = { "ROTATION_" }, value = { + ROTATION_NONE, + ROTATION_LANDSCAPE, + ROTATION_SEASCAPE, + ROTATION_UPSIDE_DOWN, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Rotation {}; + + /** + * @return the current rotation, differentiating between none (rot_0), landscape (rot_90), and + * seascape (rot_180). upside down is not distinguished here + */ + @Rotation public static int getRotation(Context context) { int rot = context.getDisplay().getRotation(); if (rot == Surface.ROTATION_90) { @@ -35,6 +59,11 @@ public class RotationUtils { } } + /** + * @return the current rotation, differentiating between landscape (rot_90), seascape + * (rot_270), and upside down (rot_180) + */ + @Rotation public static int getExactRotation(Context context) { int rot = context.getDisplay().getRotation(); if (rot == Surface.ROTATION_90) { @@ -47,4 +76,49 @@ public class RotationUtils { return ROTATION_NONE; } } + + /** * To string */ + public static String toString(@Rotation int rot) { + switch (rot) { + case ROTATION_NONE: + return "None (0)"; + case ROTATION_LANDSCAPE: + return "Landscape (1)"; + case ROTATION_UPSIDE_DOWN: + return "Upside down (2)"; + case ROTATION_SEASCAPE: + return "Seascape (3)"; + default: + return "Unknown (" + rot + ")"; + } + } + + /** + * Create a Resources using the specified rotation for the configuration. Use this to retrieve + * resources in values or values-land without needing an actual rotation to happen. + * + * @param rot the target rotation for which to create the resources + * @param context a context + * @return a Resources object configured for the given orientation + */ + public static Resources getResourcesForRotation(@Rotation int rot, Context context) { + int orientation; + switch (rot) { + case ROTATION_NONE: + case ROTATION_UPSIDE_DOWN: + orientation = ORIENTATION_PORTRAIT; + break; + case ROTATION_LANDSCAPE: + case ROTATION_SEASCAPE: + orientation = ORIENTATION_LANDSCAPE; + break; + + default: + throw new IllegalArgumentException("Unknown rotation: " + rot); + } + Configuration c = context.getResources().getConfiguration(); + c.orientation = orientation; + Context rotated = context.createConfigurationContext(c); + return rotated.getResources(); + } }