diff options
Diffstat (limited to 'quickstep/src')
12 files changed, 867 insertions, 33 deletions
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java index 540f748313..86ac39f5a2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java +++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java @@ -27,6 +27,7 @@ import android.widget.ImageView; import com.android.launcher3.R; import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; +import com.android.launcher3.taskbar.contextual.RotationContextButton; /** * Creates Buttons for Taskbar for 3 button nav. @@ -68,6 +69,11 @@ public class ButtonProvider { return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH); } + public RotationContextButton getContextualRotation() { + // Rotation suggestion button + return new RotationContextButton(mContext); + } + private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) { ImageView buttonView = new ImageView(mContext); buttonView.setImageResource(drawableId); diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java index 287caab44b..d581302e62 100644 --- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java @@ -25,7 +25,6 @@ import com.android.launcher3.views.ActivityContext; public class ImeBarView extends RelativeLayout { - private ButtonProvider mButtonProvider; private View mImeView; public ImeBarView(Context context) { @@ -41,8 +40,8 @@ public class ImeBarView extends RelativeLayout { } public void init(ButtonProvider buttonProvider) { - mButtonProvider = buttonProvider; - + // TODO (b/187966005), maybe need to replace ime switcher button with + // RotationContextButton when device rotates ActivityContext context = getActivityContext(); RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams( context.getDeviceProfile().iconSizePx, @@ -56,13 +55,13 @@ public class ImeBarView extends RelativeLayout { downParams.addRule(ALIGN_PARENT_START); // Down Arrow - View downView = mButtonProvider.getDown(); + View downView = buttonProvider.getDown(); downView.setLayoutParams(downParams); downView.setRotation(-90); addView(downView); // IME switcher button - mImeView = mButtonProvider.getImeSwitcher(); + mImeView = buttonProvider.getImeSwitcher(); mImeView.setLayoutParams(imeParams); addView(mImeView); } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index ee57dd9fe0..f124de79ba 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -32,7 +32,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.states.StateAnimationConfig; - /** * A data source which integrates with a Launcher instance * TODO: Rename to have Launcher prefix @@ -51,6 +50,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private @Nullable Animator mAnimator; private boolean mIsAnimatingToLauncher; + private ContextualRotationNotifier mContextualRotationNotifier; public LauncherTaskbarUIController( BaseQuickstepLauncher launcher, TaskbarActivityContext context) { @@ -67,7 +67,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } @Override - protected void onCreate() { + protected void onCreate(ContextualRotationNotifier notifier) { + mContextualRotationNotifier = notifier; mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController); mTaskbarAnimationController.init(); mHotseatController.init(); @@ -82,6 +83,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { // End this first, in case it relies on properties that are about to be cleaned up. mAnimator.end(); } + mContextualRotationNotifier = null; mTaskbarStateHandler.setAnimationController(null); mTaskbarAnimationController.cleanup(); mHotseatController.cleanup(); @@ -105,6 +107,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override public void updateTaskbarVisibilityAlpha(float alpha) { mTaskbarView.setAlpha(alpha); + if (mContextualRotationNotifier != null) { + mContextualRotationNotifier.onTaskbarVisibilityChanged(alpha == 1); + } } @Override @@ -272,4 +277,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController { void updateTaskbarScale(float scale); void updateTaskbarTranslationY(float translationY); } + + public interface ContextualRotationNotifier { + void onTaskbarVisibilityChanged(boolean showing); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 10ffefb047..d51506cd6c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -51,6 +51,7 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; +import com.android.launcher3.taskbar.contextual.RotationButtonController; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Themes; @@ -88,7 +89,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ private int mLastRequestedNonFullscreenHeight; private final SysUINavigationMode.Mode mNavMode; + private final SystemTaskbarNotificationManager mSystemTaskbarNotificationManager; private final TaskbarNavButtonController mNavButtonController; + private final RotationButtonController mRotationButtonController; private final boolean mIsSafeModeEnabled; @@ -98,12 +101,43 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ private final View.OnClickListener mOnTaskbarIconClickListener; private final View.OnLongClickListener mOnTaskbarIconLongClickListener; + private final TaskbarManager.SystemTaskbarNotifier mSystemTaskbarNotifier = + new TaskbarManager.SystemTaskbarNotifier() { + @Override + public void updateImeStatus(int displayId, int vis, int backDisposition, + boolean showImeSwitcher) { + /* + * When in 3 button nav, sysui flags don't get called since we prevent + * sysui nav bar from instantiating at all, which is what's responsible for + * sending sysui state flags over. + */ + mIconController.updateImeStatus(displayId, vis, showImeSwitcher); + } + + @Override + public void onRotationProposal(int rotation, boolean isValid) { + mRotationButtonController.onRotationProposal(rotation, isValid); + } + + @Override + public void disable(int displayId, int state1, int state2, boolean animate) { + mRotationButtonController.onDisable2FlagChanged(state2); + } + + @Override + public void onSystemBarAttributesChanged(int displayId, int behavior) { + mRotationButtonController.onBehaviorChanged(displayId, behavior); + } + }; + public TaskbarActivityContext(Context windowContext, DeviceProfile dp, - TaskbarNavButtonController buttonController) { + TaskbarNavButtonController buttonController, + SystemTaskbarNotificationManager systemTaskbarNotificationManager) { super(windowContext, Themes.getActivityThemeRes(windowContext)); mDeviceProfile = dp; mNavButtonController = buttonController; mNavMode = SysUINavigationMode.getMode(windowContext); + mSystemTaskbarNotificationManager = systemTaskbarNotificationManager; mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode", () -> getPackageManager().isSafeMode()); @@ -118,7 +152,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ mLayoutInflater = LayoutInflater.from(this).cloneInContext(this); mDragLayer = (TaskbarDragLayer) mLayoutInflater .inflate(R.layout.taskbar, null, false); - mIconController = new TaskbarIconController(this, mDragLayer); + + mRotationButtonController = new RotationButtonController(this, + R.color.popup_color_primary_light, R.color.popup_color_primary_light); + mIconController = new TaskbarIconController(this, mDragLayer, + mRotationButtonController); Display display = windowContext.getDisplay(); Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY @@ -149,8 +187,13 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT } ); - mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener); + mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener, + mNavMode); mWindowManager.addView(mDragLayer, mWindowLayoutParams); + if (mNavMode == Mode.THREE_BUTTONS) { + mSystemTaskbarNotificationManager + .registerSystemTaskbarNotifications(mSystemTaskbarNotifier); + } } public boolean canShowNavButtons() { @@ -189,7 +232,10 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ mUIController.onDestroy(); mUIController = uiController; mIconController.setUIController(mUIController); - mUIController.onCreate(); + mUIController.onCreate(mRotationButtonController::onTaskBarVisibilityChange); + if (mNavMode == Mode.THREE_BUTTONS) { + mRotationButtonController.init(); + } } /** @@ -199,6 +245,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ setUIController(TaskbarUIController.DEFAULT); mIconController.onDestroy(); mWindowManager.removeViewImmediate(mDragLayer); + if (mNavMode == Mode.THREE_BUTTONS) { + mSystemTaskbarNotificationManager.removeSystemTaskbarNotifications( + mSystemTaskbarNotifier); + mRotationButtonController.cleanup(); + } } void onNavigationButtonClick(@TaskbarButton int buttonType) { @@ -213,16 +264,6 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ } /** - * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from - * instantiating at all, which is what's responsible for sending sysui state flags over. - * - * @param vis IME visibility flag - */ - public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) { - mIconController.updateImeStatus(displayId, vis, showImeSwitcher); - } - - /** * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. */ public void setTaskbarWindowFullscreen(boolean fullscreen) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java index cf0694ba60..5d4b8b7f4e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java @@ -31,6 +31,8 @@ import androidx.annotation.NonNull; import com.android.launcher3.R; import com.android.launcher3.anim.AlphaUpdateListener; +import com.android.launcher3.taskbar.contextual.RotationButtonController; +import com.android.quickstep.SysUINavigationMode; import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; /** @@ -45,18 +47,22 @@ public class TaskbarIconController { private final TaskbarView mTaskbarView; private final ImeBarView mImeBarView; + private final RotationButtonController mRotationButtonController; @NonNull private TaskbarUIController mUIController = TaskbarUIController.DEFAULT; - TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) { + TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer, + RotationButtonController rotationButtonController) { mActivity = activity; mDragLayer = dragLayer; mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view); mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view); + mRotationButtonController = rotationButtonController; } - public void init(OnClickListener clickListener, OnLongClickListener longClickListener) { + public void init(OnClickListener clickListener, OnLongClickListener longClickListener, + SysUINavigationMode.Mode navMode) { mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) -> mUIController.alignRealHotseatWithTaskbar()); @@ -67,6 +73,9 @@ public class TaskbarIconController { mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; mDragLayer.init(new TaskbarDragLayerCallbacks(), mTaskbarView); + if (navMode == SysUINavigationMode.Mode.THREE_BUTTONS) { + mRotationButtonController.setRotationButton(mTaskbarView.getContextualRotationButton()); + } } public void onDestroy() { @@ -127,6 +136,12 @@ public class TaskbarIconController { mTaskbarView.mSystemButtonContainer, mTempRect); insetsInfo.touchableRegion.set(mTempRect); } + if (mTaskbarView.mContextualButtonContainer.getVisibility() == VISIBLE) { + mDragLayer.getDescendantRectRelativeToSelf( + mTaskbarView.mContextualButtonContainer, mTempRect); + insetsInfo.touchableRegion.union(mTempRect); + } + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index d026bfbf37..b9acee8160 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -40,11 +40,14 @@ import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.TouchInteractionService; +import java.util.ArrayList; +import java.util.List; + /** - * Class to manager taskbar lifecycle + * Class to manage taskbar lifecycle */ public class TaskbarManager implements DisplayController.DisplayInfoChangeListener, - SysUINavigationMode.NavigationModeChangeListener { + SysUINavigationMode.NavigationModeChangeListener, SystemTaskbarNotificationManager { private final Context mContext; private final DisplayController mDisplayController; @@ -59,6 +62,8 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen private boolean mUserUnlocked = false; + private List<SystemTaskbarNotifier> mSystemTaskbarNotifiers = new ArrayList<>(); + public TaskbarManager(TouchInteractionService service) { mDisplayController = DisplayController.INSTANCE.get(service); mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service); @@ -124,7 +129,7 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen return; } mTaskbarActivityContext = new TaskbarActivityContext( - mContext, dp.copy(mContext), mNavButtonController); + mContext, dp.copy(mContext), mNavButtonController, this); mTaskbarActivityContext.init(); if (mLauncher != null) { mTaskbarActivityContext.setUIController( @@ -132,6 +137,9 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen } } + // TODO - I don't think this is the best place for these pass through methods, + // maybe directly in TaskbarIconController? + /** * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags} * @param systemUiStateFlags The latest SystemUiStateFlags @@ -143,6 +151,16 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen } } + public void registerSystemTaskbarNotifications(SystemTaskbarNotifier notifier) { + if (!mSystemTaskbarNotifiers.contains(notifier)) { + mSystemTaskbarNotifiers.add(notifier); + } + } + + public void removeSystemTaskbarNotifications(SystemTaskbarNotifier notifier) { + mSystemTaskbarNotifiers.remove(notifier); + } + /** * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from * instantiating at all, which is what's responsible for sending sysui state flags over. @@ -153,8 +171,26 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen */ public void updateImeStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher) { - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher); + for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) { + notifier.updateImeStatus(displayId, vis, backDisposition, showImeSwitcher); + } + } + + public void onRotationProposal(int rotation, boolean isValid) { + for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) { + notifier.onRotationProposal(rotation, isValid); + } + } + + public void disable(int displayId, int state1, int state2, boolean animate) { + for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) { + notifier.disable(displayId, state1, state2, animate); + } + } + + public void onSystemBarAttributesChanged(int displayId, int behavior) { + for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) { + notifier.onSystemBarAttributesChanged(displayId, behavior); } } @@ -166,4 +202,18 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen mDisplayController.removeChangeListener(this); mSysUINavigationMode.removeModeChangeListener(this); } + + public interface SystemTaskbarNotifier { + void updateImeStatus(int displayId, int vis, int backDisposition, + boolean showImeSwitcher); + void onRotationProposal(int rotation, boolean isValid); + void disable(int displayId, int state1, int state2, boolean animate); + void onSystemBarAttributesChanged(int displayId, int behavior); + + } } + +interface SystemTaskbarNotificationManager { + void registerSystemTaskbarNotifications(TaskbarManager.SystemTaskbarNotifier notifier); + void removeSystemTaskbarNotifications(TaskbarManager.SystemTaskbarNotifier notifier); +}
\ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 50adeadbfc..f7a5618803 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -27,7 +27,7 @@ public class TaskbarUIController { */ public void alignRealHotseatWithTaskbar() { } - protected void onCreate() { } + protected void onCreate(LauncherTaskbarUIController.ContextualRotationNotifier notifier) { } protected void onDestroy() { } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index ac7035876e..5c89f8c21a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -42,6 +42,7 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.taskbar.contextual.RotationContextButton; import com.android.launcher3.views.ActivityContext; /** @@ -67,6 +68,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa LinearLayout mSystemButtonContainer; LinearLayout mHotseatIconsContainer; + LinearLayout mContextualButtonContainer; // Delegate touches to the closest view if within mIconTouchSize. private boolean mDelegateTargeted; @@ -79,6 +81,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa /** Provider of buttons added to taskbar in 3 button nav */ private ButtonProvider mButtonProvider; + private RotationContextButton mContextualRotationButton; private boolean mDisableRelayout; private boolean mAreHolesAllowed; @@ -112,8 +115,9 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa @Override protected void onFinishInflate() { super.onFinishInflate(); - mSystemButtonContainer = findViewById(R.id.system_button_layout); + mSystemButtonContainer = findViewById(R.id.nav_button_layout); mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout); + mContextualButtonContainer = findViewById(R.id.contextual_button_layout); } protected void init(TaskbarIconController.TaskbarViewCallbacks callbacks, @@ -132,6 +136,10 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons; updateHotseatItems(new ItemInfo[numHotseatIcons]); + + if (mActivityContext.canShowNavButtons()) { + createContextualRegion(); + } } /** @@ -378,6 +386,16 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } } + private void createContextualRegion() { + mContextualRotationButton = mButtonProvider.getContextualRotation(); + mContextualRotationButton.setVisibility(GONE); + mContextualButtonContainer.addView(mContextualRotationButton); + } + + @Nullable + public RotationContextButton getContextualRotationButton() { + return mContextualRotationButton; + } // FolderIconParent implemented methods. @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java new file mode 100644 index 0000000000..d42107742e --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java @@ -0,0 +1,46 @@ +/* + * Copyright 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.launcher3.taskbar.contextual; + +import android.graphics.drawable.AnimatedVectorDrawable; +import android.view.View; + +/** + * Interface of a rotation button that interacts {@link RotationButtonController}. + * This interface exists because of the two different styles of rotation button in Sysui, + * one in contextual for 3 button nav and a floating rotation button for gestural. + * Keeping the interface for eventual migration of floating button, so some methods are + * pass through to "super" while others are trivially implemented. + * + * Changes: + * * Directly use AnimatedVectorDrawable instead of KeyButtonDrawable + */ +public interface RotationButton { + void setRotationButtonController(RotationButtonController rotationButtonController); + View getCurrentView(); + boolean show(); + boolean hide(); + boolean isVisible(); + void updateIcon(int lightIconColor, int darkIconColor); + void setOnClickListener(View.OnClickListener onClickListener); + void setOnHoverListener(View.OnHoverListener onHoverListener); + AnimatedVectorDrawable getImageDrawable(); + void setDarkIntensity(float darkIntensity); + default boolean acceptRotationProposal() { + return getCurrentView() != null; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java new file mode 100644 index 0000000000..6f6abc2533 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java @@ -0,0 +1,512 @@ +/* + * Copyright 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.launcher3.taskbar.contextual; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.ColorInt; +import android.annotation.DrawableRes; +import android.annotation.SuppressLint; +import android.app.StatusBarManager; +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Log; +import android.view.IRotationWatcher; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.WindowInsetsController; +import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; +import com.android.internal.view.RotationPolicy; +import com.android.launcher3.R; +import com.android.launcher3.util.DisplayController; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.utilities.ViewRippler; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.TaskStackChangeListeners; + +import java.util.Optional; + +/** + * Copied over from the SysUI equivalent class. Known issues/things not ported over + * * When rotation button visible and in auto-hide mode, we ask auto-hide controller to + * keep the navbar around longer. Will need to implement if we use auto-hide on taskbar + * + * Contains logic that deals with showing a rotate suggestion button with animation. + */ +public class RotationButtonController { + + private static final String TAG = "StatusBar/RotationButtonController"; + private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; + private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000; + + private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; + + private final Context mContext; + private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); + private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + private final ViewRippler mViewRippler = new ViewRippler(); + private final DisplayController mDisplayController; + private RotationButton mRotationButton; + + private int mLastRotationSuggestion; + private boolean mPendingRotationSuggestion; + private boolean mHoveringRotationSuggestion; + private final AccessibilityManager mAccessibilityManager; + private final TaskStackListenerImpl mTaskStackListener; + private boolean mListenersRegistered = false; + private boolean mIsTaskbarShowing; + @SuppressLint("InlinedApi") + private @WindowInsetsController.Behavior + int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; + private boolean mSkipOverrideUserLockPrefsOnce; + private final int mLightIconColor; + private final int mDarkIconColor; + private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90; + + private final Runnable mRemoveRotationProposal = + () -> setRotateSuggestionButtonState(false /* visible */); + private final Runnable mCancelPendingRotationProposal = + () -> mPendingRotationSuggestion = false; + private Animator mRotateHideAnimator; + + + private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() { + @Override + public void onRotationChanged(final int rotation) throws RemoteException { + // We need this to be scheduled as early as possible to beat the redrawing of + // window in response to the orientation change. + mMainThreadHandler.postAtFrontOfQueue(() -> { + // If the screen rotation changes while locked, potentially update lock to flow with + // new screen rotation and hide any showing suggestions. + if (isRotationLocked()) { + if (shouldOverrideUserLockPrefs(rotation)) { + setRotationLockedAtAngle(rotation); + } + setRotateSuggestionButtonState(false /* visible */, true /* forced */); + } + }); + } + }; + + /** + * Determines if rotation suggestions disabled2 flag exists in flag + * @param disable2Flags see if rotation suggestion flag exists in this flag + * @return whether flag exists + */ + static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) { + return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0; + } + + public RotationButtonController(Context context, @ColorInt int lightIconColor, + @ColorInt int darkIconColor) { + mContext = context; + mLightIconColor = lightIconColor; + mDarkIconColor = darkIconColor; + + mAccessibilityManager = AccessibilityManager.getInstance(context); + mTaskStackListener = new TaskStackListenerImpl(); + mDisplayController = DisplayController.INSTANCE.getNoCreate(); + } + + public void setRotationButton(RotationButton rotationButton) { + mRotationButton = rotationButton; + mRotationButton.setRotationButtonController(this); + mRotationButton.setOnClickListener(this::onRotateSuggestionClick); + mRotationButton.setOnHoverListener(this::onRotateSuggestionHover); + } + + public void init() { + registerListeners(); + if (mDisplayController.getInfo().id != DEFAULT_DISPLAY) { + // Currently there is no accelerometer sensor on non-default display, disable fixed + // rotation for non-default display + onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS); + } + } + + public void cleanup() { + unregisterListeners(); + } + + private void registerListeners() { + if (mListenersRegistered) { + return; + } + + mListenersRegistered = true; + try { + WindowManagerGlobal.getWindowManagerService() + .watchRotation(mRotationWatcher, mDisplayController.getInfo().id); + } catch (IllegalArgumentException e) { + mListenersRegistered = false; + Log.w(TAG, "RegisterListeners for the display failed"); + } catch (RemoteException e) { + Log.e(TAG, "RegisterListeners caught a RemoteException", e); + return; + } + + TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); + } + + void unregisterListeners() { + if (!mListenersRegistered) { + return; + } + + mListenersRegistered = false; + try { + WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher); + } catch (RemoteException e) { + Log.e(TAG, "UnregisterListeners caught a RemoteException", e); + return; + } + + TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); + } + + void setRotationLockedAtAngle(int rotationSuggestion) { + RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion); + } + + public boolean isRotationLocked() { + return RotationPolicy.isRotationLocked(mContext); + } + + public void setRotateSuggestionButtonState(boolean visible) { + setRotateSuggestionButtonState(visible, false /* force */); + } + + void setRotateSuggestionButtonState(final boolean visible, final boolean force) { + // At any point the button can become invisible because an a11y service became active. + // Similarly, a call to make the button visible may be rejected because an a11y service is + // active. Must account for this. + // Rerun a show animation to indicate change but don't rerun a hide animation + if (!visible && !mRotationButton.isVisible()) return; + + final View view = mRotationButton.getCurrentView(); + if (view == null) return; + + final AnimatedVectorDrawable currentDrawable = mRotationButton.getImageDrawable(); + if (currentDrawable == null) return; + + // Clear any pending suggestion flag as it has either been nullified or is being shown + mPendingRotationSuggestion = false; + mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal); + + // Handle the visibility change and animation + if (visible) { // Appear and change (cannot force) + // Stop and clear any currently running hide animations + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { + mRotateHideAnimator.cancel(); + } + mRotateHideAnimator = null; + + // Reset the alpha if any has changed due to hide animation + view.setAlpha(1f); + + // Run the rotate icon's animation if it has one + currentDrawable.reset(); + currentDrawable.start(); + + // TODO(b/187754252): No idea why this doesn't work. If we remove the "false" + // we see the animation show the pressed state... but it only shows the first time. + if (!isRotateSuggestionIntroduced()) mViewRippler.start(view); + + // Set visibility unless a11y service is active. + mRotationButton.show(); + } else { // Hide + mViewRippler.stop(); // Prevent any pending ripples, force hide or not + + if (force) { + // If a hide animator is running stop it and make invisible + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { + mRotateHideAnimator.pause(); + } + mRotationButton.hide(); + return; + } + + // Don't start any new hide animations if one is running + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; + + ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f); + fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); + fadeOut.setInterpolator(LINEAR); + fadeOut.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRotationButton.hide(); + } + }); + + mRotateHideAnimator = fadeOut; + fadeOut.start(); + } + } + + void setDarkIntensity(float darkIntensity) { + mRotationButton.setDarkIntensity(darkIntensity); + } + + public void onRotationProposal(int rotation, boolean isValid) { + int windowRotation = mDisplayController.getInfo().rotation; + + if (!mRotationButton.acceptRotationProposal()) { + return; + } + + // This method will be called on rotation suggestion changes even if the proposed rotation + // is not valid for the top app. Use invalid rotation choices as a signal to remove the + // rotate button if shown. + if (!isValid) { + setRotateSuggestionButtonState(false /* visible */); + return; + } + + // If window rotation matches suggested rotation, remove any current suggestions + if (rotation == windowRotation) { + mMainThreadHandler.removeCallbacks(mRemoveRotationProposal); + setRotateSuggestionButtonState(false /* visible */); + return; + } + + // Prepare to show the navbar icon by updating the icon style to change anim params + mLastRotationSuggestion = rotation; // Remember rotation for click + final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation); + if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) { + mIconResId = rotationCCW + ? R.drawable.ic_sysbar_rotate_button_ccw_start_90 + : R.drawable.ic_sysbar_rotate_button_cw_start_90; + } else { // 90 or 270 + mIconResId = rotationCCW + ? R.drawable.ic_sysbar_rotate_button_ccw_start_0 + : R.drawable.ic_sysbar_rotate_button_ccw_start_0; + } + mRotationButton.updateIcon(mLightIconColor, mDarkIconColor); + + if (canShowRotationButton()) { + // The navbar is visible / it's in visual immersive mode, so show the icon right away + showAndLogRotationSuggestion(); + } else { + // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become + // visible given some time limit. + mPendingRotationSuggestion = true; + mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal); + mMainThreadHandler.postDelayed(mCancelPendingRotationProposal, + NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS); + } + } + + public void onDisable2FlagChanged(int state2) { + final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2); + if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled(); + } + + public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) { + if (mDisplayController.getInfo().id != displayId) { + return; + } + + if (mBehavior != behavior) { + mBehavior = behavior; + showPendingRotationButtonIfNeeded(); + } + } + + public void onTaskBarVisibilityChange(boolean showing) { + if (mIsTaskbarShowing != showing) { + mIsTaskbarShowing = showing; + showPendingRotationButtonIfNeeded(); + } + } + + private void showPendingRotationButtonIfNeeded() { + if (canShowRotationButton() && mPendingRotationSuggestion) { + showAndLogRotationSuggestion(); + } + } + + /** Return true when either the task bar is visible or it's in visual immersive mode. */ + @SuppressLint("InlinedApi") + private boolean canShowRotationButton() { + return mIsTaskbarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT; + } + + public @DrawableRes + int getIconResId() { + return mIconResId; + } + + public @ColorInt int getLightIconColor() { + return mLightIconColor; + } + + public @ColorInt int getDarkIconColor() { + return mDarkIconColor; + } + + private void onRotateSuggestionClick(View v) { + mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); + incrementNumAcceptedRotationSuggestionsIfNeeded(); + setRotationLockedAtAngle(mLastRotationSuggestion); + } + + private boolean onRotateSuggestionHover(View v, MotionEvent event) { + final int action = event.getActionMasked(); + mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER) + || (action == MotionEvent.ACTION_HOVER_MOVE); + rescheduleRotationTimeout(true /* reasonHover */); + return false; // Must return false so a11y hover events are dispatched correctly. + } + + private void onRotationSuggestionsDisabled() { + // Immediately hide the rotate button and clear any planned removal + setRotateSuggestionButtonState(false /* visible */, true /* force */); + mMainThreadHandler.removeCallbacks(mRemoveRotationProposal); + } + + private void showAndLogRotationSuggestion() { + setRotateSuggestionButtonState(true /* visible */); + rescheduleRotationTimeout(false /* reasonHover */); + mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN); + } + + /** + * Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to + * avoid losing original user rotation when display rotation is changed by entering the fixed + * orientation overview. + */ + void setSkipOverrideUserLockPrefsOnce() { + mSkipOverrideUserLockPrefsOnce = true; + } + + private boolean shouldOverrideUserLockPrefs(final int rotation) { + if (mSkipOverrideUserLockPrefsOnce) { + mSkipOverrideUserLockPrefsOnce = false; + return false; + } + // Only override user prefs when returning to the natural rotation (normally portrait). + // Don't let apps that force landscape or 180 alter user lock. + return rotation == NATURAL_ROTATION; + } + + private void rescheduleRotationTimeout(final boolean reasonHover) { + // May be called due to a new rotation proposal or a change in hover state + if (reasonHover) { + // Don't reschedule if a hide animator is running + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; + // Don't reschedule if not visible + if (!mRotationButton.isVisible()) return; + } + + // Stop any pending removal + mMainThreadHandler.removeCallbacks(mRemoveRotationProposal); + // Schedule timeout + mMainThreadHandler.postDelayed(mRemoveRotationProposal, + computeRotationProposalTimeout()); + } + + private int computeRotationProposalTimeout() { + return mAccessibilityManager.getRecommendedTimeoutMillis( + mHoveringRotationSuggestion ? 16000 : 5000, + AccessibilityManager.FLAG_CONTENT_CONTROLS); + } + + private boolean isRotateSuggestionIntroduced() { + ContentResolver cr = mContext.getContentResolver(); + return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0) + >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION; + } + + private void incrementNumAcceptedRotationSuggestionsIfNeeded() { + // Get the number of accepted suggestions + ContentResolver cr = mContext.getContentResolver(); + final int numSuggestions = Settings.Secure.getInt(cr, + Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0); + + // Increment the number of accepted suggestions only if it would change intro mode + if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) { + Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, + numSuggestions + 1); + } + } + + private class TaskStackListenerImpl extends TaskStackChangeListener { + // Invalidate any rotation suggestion on task change or activity orientation change + // Note: all callbacks happen on main thread + + @Override + public void onTaskStackChanged() { + setRotateSuggestionButtonState(false /* visible */); + } + + @Override + public void onTaskRemoved(int taskId) { + setRotateSuggestionButtonState(false /* visible */); + } + + @Override + public void onTaskMovedToFront(int taskId) { + setRotateSuggestionButtonState(false /* visible */); + } + + @Override + public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { + // Only hide the icon if the top task changes its requestedOrientation + // Launcher can alter its requestedOrientation while it's not on top, don't hide on this + Optional.ofNullable(ActivityManagerWrapper.getInstance()) + .map(ActivityManagerWrapper::getRunningTask) + .ifPresent(a -> { + if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */); + }); + } + } + + enum RotationButtonEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The rotation button was shown") + ROTATION_SUGGESTION_SHOWN(206), + @UiEvent(doc = "The rotation button was clicked") + ROTATION_SUGGESTION_ACCEPTED(207); + + private final int mId; + RotationButtonEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + } +} + diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationContextButton.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationContextButton.java new file mode 100644 index 0000000000..7ad3191c49 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationContextButton.java @@ -0,0 +1,112 @@ +/* + * Copyright 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.launcher3.taskbar.contextual; + +import android.content.Context; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.view.View; +import android.widget.ImageView; + +import com.android.launcher3.R; + +/** Containing logic for the rotation button in nav bar. */ +public class RotationContextButton extends ImageView implements RotationButton { + + private AnimatedVectorDrawable mImageDrawable; + + public RotationContextButton(Context context) { + super(context); + setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect); + } + + @Override + public void setRotationButtonController(RotationButtonController rotationButtonController) { + // TODO(b/187754252) UI polish, different icons based on light/dark context, etc + mImageDrawable = (AnimatedVectorDrawable) getContext() + .getDrawable(rotationButtonController.getIconResId()); + setImageDrawable(mImageDrawable); + mImageDrawable.setCallback(this); + } + + @Override + public View getCurrentView() { + return this; + } + + @Override + public boolean show() { + setVisibility(VISIBLE); + return true; + } + + @Override + public boolean hide() { + setVisibility(GONE); + return true; + } + + @Override + public boolean isVisible() { + return getVisibility() == VISIBLE; + } + + @Override + public void updateIcon(int lightIconColor, int darkIconColor) { + // TODO(b/187754252): UI Polish + } + + @Override + public void setOnClickListener(View.OnClickListener onClickListener) { + super.setOnClickListener(onClickListener); + } + + @Override + public void setOnHoverListener(View.OnHoverListener onHoverListener) { + super.setOnHoverListener(onHoverListener); + } + + @Override + public AnimatedVectorDrawable getImageDrawable() { + return mImageDrawable; + } + + @Override + public void setDarkIntensity(float darkIntensity) { + // TODO(b/187754252) UI polish + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + + if (visibility != View.VISIBLE && mImageDrawable != null) { + mImageDrawable.clearAnimationCallbacks(); + mImageDrawable.reset(); + } + + // Start the rotation animation once it becomes visible + if (visibility == View.VISIBLE && mImageDrawable != null) { + mImageDrawable.reset(); + mImageDrawable.start(); + } + } + + @Override + public boolean acceptRotationProposal() { + return isAttachedToWindow(); + } +} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index e52405bbd5..afafce7382 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -261,7 +261,7 @@ public class TouchInteractionService extends Service implements PluginListener<O } @Override - public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) { + public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) { WindowBounds wb = new WindowBounds(bounds, insets); MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb)); } @@ -269,8 +269,34 @@ public class TouchInteractionService extends Service implements PluginListener<O @Override public void onImeWindowStatusChanged(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { - MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus( - displayId, vis, backDisposition, showImeSwitcher)); + executeForTaskbarManager(() -> mTaskbarManager + .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher)); + } + + @Override + public void onRotationProposal(int rotation, boolean isValid) { + executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid)); + } + + @Override + public void disable(int displayId, int state1, int state2, boolean animate) { + executeForTaskbarManager(() -> mTaskbarManager + .disable(displayId, state1, state2, animate)); + } + + @Override + public void onSystemBarAttributesChanged(int displayId, int behavior) { + executeForTaskbarManager(() -> mTaskbarManager + .onSystemBarAttributesChanged(displayId, behavior)); + } + + private void executeForTaskbarManager(final Runnable r) { + MAIN_EXECUTOR.execute(() -> { + if (mTaskbarManager == null) { + return; + } + r.run(); + }); } public TaskbarManager getTaskbarManager() { |