diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 31266db9e1446715f23b9b6762ba23b5e8737fa4..68022b4fee24baf535435ac208ab79a3075b1325 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -26,6 +26,7 @@ import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE; import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; @@ -56,6 +57,8 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.Binder; @@ -71,6 +74,7 @@ import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.Log; import android.view.Display; +import android.view.Gravity; import android.view.InsetsState.InternalInsetsType; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -104,6 +108,7 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; @@ -133,7 +138,7 @@ import dagger.Lazy; * on clicks and view states of the nav bar. */ public class NavigationBarFragment extends LifecycleFragment implements Callbacks, - NavigationModeController.ModeChangedListener { + NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener { public static final String TAG = "NavigationBar"; private static final boolean DEBUG = false; @@ -141,6 +146,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private static final String EXTRA_DISABLE2_STATE = "disabled2_state"; private static final String EXTRA_APPEARANCE = "appearance"; private static final String EXTRA_TRANSIENT_STATE = "transient_state"; + private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + /** Allow some time inbetween the long press for back and recents. */ private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; @@ -199,6 +206,23 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private boolean mIsOnDefaultDisplay; public boolean mHomeBlockedThisTouch; + /** + * When user is QuickSwitching between apps of different orientations, we'll draw a fake + * home handle on the orientation they originally touched down to start their swipe + * gesture to indicate to them that they can continue in that orientation without having to + * rotate the phone + * The secondary handle will show when we get + * {@link OverviewProxyListener#onQuickSwitchToNewTask(int)} callback with the + * original handle hidden and we'll flip the visibilities once the + * {@link #mTasksFrozenListener} fires + */ + private NavigationHandle mOrientationHandle; + private WindowManager.LayoutParams mOrientationParams; + private boolean mFrozenTasks; + private int mStartingQuickSwitchRotation; + private int mCurrentRotation; + private boolean mFixedRotationEnabled; + /** Only for default display */ @Nullable private AssistHandleViewController mAssistHandlerViewController; @@ -248,6 +272,12 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mShadeController.collapsePanel(true /* animate */); } + @Override + public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { + mStartingQuickSwitchRotation = rotation; + orientSecondaryHomeHandle(); + } + @Override public void startAssistant(Bundle bundle) { mAssistManager.startAssist(bundle); @@ -271,6 +301,22 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + private TaskStackChangeListener mTasksFrozenListener = new TaskStackChangeListener() { + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + mFrozenTasks = frozen; + orientSecondaryHomeHandle(); + } + }; + + private NavigationBarTransitions.DarkIntensityListener mOrientationHandleIntensityListener = + new NavigationBarTransitions.DarkIntensityListener() { + @Override + public void onDarkIntensity(float darkIntensity) { + mOrientationHandle.setDarkIntensity(darkIntensity); + } + }; + private final ContextButtonListener mRotationButtonListener = (button, visible) -> { if (visible) { // If the button will actually become visible and the navbar is about to hide, @@ -294,6 +340,14 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + private final ContentObserver mFixedRotationObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updatedFixedRotation(); + } + }; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -351,6 +405,10 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME), + false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL); + if (savedInstanceState != null) { mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0); @@ -376,6 +434,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mNavigationModeController.removeListener(this); mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); mContentResolver.unregisterContentObserver(mAssistContentObserver); + mContentResolver.unregisterContentObserver(mFixedRotationObserver); DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); } @@ -406,6 +465,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } mNavigationBarView.setNavigationIconHints(mNavigationIconHints); mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); + updatedFixedRotation(); prepareNavigationBarView(); checkNavBarModes(); @@ -442,6 +502,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback new AssistHandleViewController(mHandler, mNavigationBarView); getBarTransitions().addDarkIntensityListener(mAssistHandlerViewController); } + + initSecondaryHomeHandleForRotation(); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTasksFrozenListener); } @Override @@ -458,6 +521,13 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } mOverviewProxyService.removeCallback(mOverviewProxyListener); mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTasksFrozenListener); + if (mOrientationHandle != null) { + resetSecondaryHandle(); + getContext().getSystemService(DisplayManager.class).unregisterDisplayListener(this); + getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); + mWindowManager.removeView(mOrientationHandle); + } } @Override @@ -490,6 +560,88 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback repositionNavigationBar(); } + private void initSecondaryHomeHandleForRotation() { + if (!canShowSecondaryHandle()) { + return; + } + + getContext().getSystemService(DisplayManager.class) + .registerDisplayListener(this, new Handler(Looper.getMainLooper())); + + mOrientationHandle = new VerticalNavigationHandle(getContext()); + + getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener); + mOrientationParams = new WindowManager.LayoutParams(0, 0, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_SLIPPERY, + PixelFormat.TRANSLUCENT); + mWindowManager.addView(mOrientationHandle, mOrientationParams); + mOrientationHandle.setVisibility(View.GONE); + } + + private void orientSecondaryHomeHandle() { + if (!canShowSecondaryHandle()) { + return; + } + + if (!mFrozenTasks) { + resetSecondaryHandle(); + } else { + int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); + int height = 0; + int width = 0; + Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); + switch (deltaRotation) { + case Surface.ROTATION_90: + case Surface.ROTATION_270: + height = dispSize.height(); + width = getResources() + .getDimensionPixelSize(R.dimen.navigation_bar_height); + break; + case Surface.ROTATION_180: + case Surface.ROTATION_0: + // TODO(b/152683657): Need to determine best UX for this + resetSecondaryHandle(); + return; + } + + mOrientationParams.gravity = + deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT; + mOrientationParams.height = height; + mOrientationParams.width = width; + mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); + mNavigationBarView.setVisibility(View.GONE); + mOrientationHandle.setVisibility(View.VISIBLE); + } + } + + private void resetSecondaryHandle() { + if (mOrientationHandle != null) { + // Case where nav mode is changed w/o ever invoking a quickstep + // mOrientedHandle is initialized lazily + mOrientationHandle.setVisibility(View.GONE); + } + mNavigationBarView.setVisibility(View.VISIBLE); + } + + private int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + + private void updatedFixedRotation() { + mFixedRotationEnabled = Settings.Global.getInt(getContext().getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + if (!canShowSecondaryHandle()) { + resetSecondaryHandle(); + } + } + @Override public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) { if (mNavigationBarView != null) { @@ -1115,6 +1267,10 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mNavBarMode = mode; updateScreenPinningGestures(); + if (!canShowSecondaryHandle()) { + resetSecondaryHandle(); + } + // Workaround for b/132825155, for secondary users, we currently don't receive configuration // changes on overlay package change since SystemUI runs for the system user. In this case, // trigger a new configuration change to ensure that the nav bar is updated in the same way. @@ -1159,6 +1315,34 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private final AccessibilityServicesStateChangeListener mAccessibilityListener = this::updateAccessibilityServicesState; + @Override + public void onDisplayAdded(int displayId) { + + } + + @Override + public void onDisplayRemoved(int displayId) { + + } + + @Override + public void onDisplayChanged(int displayId) { + if (!canShowSecondaryHandle()) { + return; + } + + int rotation = getContext().getResources().getConfiguration() + .windowConfiguration.getRotation(); + if (rotation != mCurrentRotation) { + mCurrentRotation = rotation; + orientSecondaryHomeHandle(); + } + } + + private boolean canShowSecondaryHandle() { + return mFixedRotationEnabled && mNavBarMode == NAV_BAR_MODE_GESTURAL; + } + private final Consumer<Integer> mRotationWatcher = rotation -> { if (mNavigationBarView != null && mNavigationBarView.needsReorient(rotation)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java index abceb11b36e99cc07eb08071a7b9166755d14a55..b87479505d00a0e33fa94bb7aa9ac7222befa5ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java @@ -32,11 +32,11 @@ import com.android.systemui.R; public class NavigationHandle extends View implements ButtonInterface { - private final Paint mPaint = new Paint(); + protected final Paint mPaint = new Paint(); private @ColorInt final int mLightColor; private @ColorInt final int mDarkColor; - private final int mRadius; - private final int mBottom; + protected final int mRadius; + protected final int mBottom; private boolean mRequiresInvalidate; public NavigationHandle(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java new file mode 100644 index 0000000000000000000000000000000000000000..a15ca9532a88ec95f530a38885094cda3e73dd04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 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.graphics.Canvas; + +import com.android.systemui.R; + +/** Temporarily shown view when using QuickSwitch to switch between apps of different rotations */ +public class VerticalNavigationHandle extends NavigationHandle { + private final int mWidth; + + public VerticalNavigationHandle(Context context) { + super(context); + mWidth = context.getResources().getDimensionPixelSize(R.dimen.navigation_home_handle_width); + } + + @Override + protected void onDraw(Canvas canvas) { + int left; + int top; + int bottom; + int right; + + int radiusOffset = mRadius * 2; + right = getWidth() - mBottom; + top = getHeight() / 2 - (mWidth / 2); /* (height of screen / 2) - (height of bar / 2) */ + left = getWidth() - mBottom - radiusOffset; + bottom = getHeight() / 2 + (mWidth / 2); + canvas.drawRoundRect(left, top, right, bottom, mRadius, mRadius, mPaint); + } +}