diff --git a/quickstep/res/drawable/ic_ime_switcher.xml b/quickstep/res/drawable/ic_ime_switcher.xml new file mode 100644 index 0000000000..a86d3904c7 --- /dev/null +++ b/quickstep/res/drawable/ic_ime_switcher.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/quickstep/res/drawable/ic_sysbar_back.xml b/quickstep/res/drawable/ic_sysbar_back.xml new file mode 100644 index 0000000000..1eea6774b1 --- /dev/null +++ b/quickstep/res/drawable/ic_sysbar_back.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/quickstep/res/drawable/ic_sysbar_home.xml b/quickstep/res/drawable/ic_sysbar_home.xml new file mode 100644 index 0000000000..b4b397b59c --- /dev/null +++ b/quickstep/res/drawable/ic_sysbar_home.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/quickstep/res/drawable/ic_sysbar_recent.xml b/quickstep/res/drawable/ic_sysbar_recent.xml new file mode 100644 index 0000000000..f8c4778a2b --- /dev/null +++ b/quickstep/res/drawable/ic_sysbar_recent.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml new file mode 100644 index 0000000000..d6160def0f --- /dev/null +++ b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index 732222ade8..240fe556da 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -26,4 +26,10 @@ android:layout_height="wrap_content" android:gravity="center"/> + + \ No newline at end of file diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml index 3bc8ddc0a8..167c7c3d0f 100644 --- a/quickstep/res/values/colors.xml +++ b/quickstep/res/values/colors.xml @@ -27,4 +27,5 @@ #101010 + #E0E0E0 \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 63c1fce328..2aac877804 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -31,8 +31,11 @@ import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.ComponentName; +import android.content.ServiceConnection; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.IBinder; import android.view.View; import androidx.annotation.Nullable; @@ -62,6 +65,7 @@ import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; +import com.android.quickstep.TouchInteractionService; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.util.SplitSelectStateController; @@ -83,6 +87,8 @@ public abstract class BaseQuickstepLauncher extends Launcher private DepthController mDepthController = new DepthController(this); private QuickstepTransitionManager mAppTransitionManager; + private ServiceConnection mTisBinderConnection; + protected TouchInteractionService.TISBinder mTisBinder; /** * Reusable command for applying the back button alpha on the background thread. @@ -104,6 +110,24 @@ public abstract class BaseQuickstepLauncher extends Launcher super.onCreate(savedInstanceState); SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this); addMultiWindowModeChangedListener(mDepthController); + setupTouchInteractionServiceBinder(); + } + + private void setupTouchInteractionServiceBinder() { + Intent intent = new Intent(this, TouchInteractionService.class); + mTisBinderConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder binder) { + mTisBinder = ((TouchInteractionService.TISBinder) binder); + mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + mTisBinder = null; + } + }; + bindService(intent, mTisBinderConnection, 0); } @Override @@ -114,6 +138,10 @@ public abstract class BaseQuickstepLauncher extends Launcher if (mTaskbarController != null) { mTaskbarController.cleanup(); mTaskbarController = null; + if (mTisBinder != null) { + mTisBinder.setTaskbarOverviewProxyDelegate(null); + unbindService(mTisBinderConnection); + } } super.onDestroy(); @@ -249,6 +277,9 @@ public abstract class BaseQuickstepLauncher extends Launcher private void addTaskbarIfNecessary() { if (mTaskbarController != null) { mTaskbarController.cleanup(); + if (mTisBinder != null) { + mTisBinder.setTaskbarOverviewProxyDelegate(null); + } mTaskbarController = null; } if (mDeviceProfile.isTaskbarPresent) { @@ -257,6 +288,9 @@ public abstract class BaseQuickstepLauncher extends Launcher mTaskbarController = new TaskbarController(this, taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome); mTaskbarController.init(); + if (mTisBinder != null) { + mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController); + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java new file mode 100644 index 0000000000..0d4130d1bc --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java @@ -0,0 +1,76 @@ +/* + * 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; + +import android.annotation.DrawableRes; +import android.content.Context; +import android.view.View; +import android.widget.ImageView; + +import com.android.launcher3.R; + +/** + * Creates Buttons for Taskbar for 3 button nav. + * Can add animations and state management for buttons in this class as things progress. + */ +public class ButtonProvider { + + private int mMarginLeftRight; + private final Context mContext; + + public ButtonProvider(Context context) { + mContext = context; + } + + public void setMarginLeftRight(int margin) { + mMarginLeftRight = margin; + } + + public View getBack() { + // Back button + return getButtonForDrawable(R.drawable.ic_sysbar_back); + } + + public View getDown() { + // Ime down button + return getButtonForDrawable(R.drawable.ic_sysbar_back); + } + + public View getHome() { + // Home button + return getButtonForDrawable(R.drawable.ic_sysbar_home); + } + + public View getRecents() { + // Recents button + return getButtonForDrawable(R.drawable.ic_sysbar_recent); + } + + public View getImeSwitcher() { + // IME Switcher Button + return getButtonForDrawable(R.drawable.ic_ime_switcher); + } + + private View getButtonForDrawable(@DrawableRes int drawableId) { + ImageView buttonView = new ImageView(mContext); + buttonView.setImageResource(drawableId); + buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect); + buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0); + return buttonView; + } + +} diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java new file mode 100644 index 0000000000..bb3669beed --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java @@ -0,0 +1,92 @@ +/* + * 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; + +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RelativeLayout; + +import com.android.launcher3.views.ActivityContext; + +public class ImeBarView extends RelativeLayout { + + private ButtonProvider mButtonProvider; + private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; + private View mImeView; + + public ImeBarView(Context context) { + this(context, null); + } + + public ImeBarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ImeBarView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void construct(ButtonProvider buttonProvider) { + mButtonProvider = buttonProvider; + } + + public void init(TaskbarController.TaskbarViewCallbacks taskbarCallbacks) { + mControllerCallbacks = taskbarCallbacks; + ActivityContext context = getActivityContext(); + RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams( + context.getDeviceProfile().iconSizePx, + context.getDeviceProfile().iconSizePx + ); + RelativeLayout.LayoutParams downParams = new RelativeLayout.LayoutParams(imeParams); + + imeParams.addRule(ALIGN_PARENT_END); + imeParams.setMarginEnd(context.getDeviceProfile().iconSizePx); + downParams.setMarginStart(context.getDeviceProfile().iconSizePx); + downParams.addRule(ALIGN_PARENT_START); + + // Down Arrow + View downView = mButtonProvider.getDown(); + downView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( + BUTTON_BACK)); + downView.setLayoutParams(downParams); + downView.setRotation(-90); + addView(downView); + + // IME switcher button + mImeView = mButtonProvider.getImeSwitcher(); + mImeView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( + BUTTON_IME_SWITCH)); + mImeView.setLayoutParams(imeParams); + addView(mImeView); + } + + public void cleanup() { + removeAllViews(); + } + + public void setImeSwitcherVisibility(boolean show) { + mImeView.setVisibility(show ? VISIBLE : GONE); + } + + private T getActivityContext() { + return ActivityContext.lookupContext(getContext()); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java index 46e4506f1f..29f6935f8a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java @@ -44,7 +44,7 @@ public class TaskbarAnimationController { private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat( this::updateVisibilityAlpha); private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat( - this::updateVisibilityAlpha); + this::updateVisibilityAlphaForIme); // Scale. private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat( @@ -110,16 +110,22 @@ public class TaskbarAnimationController { // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the // assumption being that Taskbar should always be visible regardless of the current // LauncherState if Launcher is paused. + float alphaDueToIme = mTaskbarVisibilityAlphaForIme.value; float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value, mTaskbarVisibilityAlphaForLauncherState.value); - float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value; - float taskbarAlpha = alphaDueToLauncher * alphaDueToOther; + float taskbarAlpha = alphaDueToLauncher * alphaDueToIme; mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha); // Make the nav bar invisible if taskbar is visible. setNavBarButtonAlpha(1f - taskbarAlpha); } + private void updateVisibilityAlphaForIme() { + updateVisibilityAlpha(); + float taskbarAlphaDueToIme = mTaskbarVisibilityAlphaForIme.value; + mTaskbarCallbacks.updateImeBarVisibilityAlpha(1f - taskbarAlphaDueToIme); + } + private void updateScale() { // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the // assumption being that Taskbar should always be at scale 1f regardless of the current diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index c93de00f4f..6084e10d85 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.taskbar; +import static android.view.View.GONE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -28,6 +31,7 @@ import android.app.ActivityOptions; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -48,9 +52,12 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.views.ActivityContext; import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.TouchInteractionService.TaskbarOverviewProxyDelegate; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -58,13 +65,15 @@ import com.android.systemui.shared.system.WindowManagerWrapper; /** * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView. */ -public class TaskbarController { +public class TaskbarController implements TaskbarOverviewProxyDelegate { private static final String WINDOW_TITLE = "Taskbar"; private final TaskbarContainerView mTaskbarContainerView; private final TaskbarView mTaskbarViewInApp; private final TaskbarView mTaskbarViewOnHome; + private final ImeBarView mImeBarView; + private final BaseQuickstepLauncher mLauncher; private final WindowManager mWindowManager; // Layout width and height of the Taskbar in the default state. @@ -73,9 +82,13 @@ public class TaskbarController { private final TaskbarAnimationController mTaskbarAnimationController; private final TaskbarHotseatController mHotseatController; private final TaskbarDragController mDragController; + private final TaskbarNavButtonController mNavButtonController; // Initialized in init(). private WindowManager.LayoutParams mWindowLayoutParams; + private SysUINavigationMode.Mode mNavMode = SysUINavigationMode.Mode.NO_BUTTON; + private final SysUINavigationMode.NavigationModeChangeListener mNavigationModeChangeListener = + this::onNavModeChanged; private @Nullable Animator mAnimator; private boolean mIsAnimatingToLauncher; @@ -85,10 +98,14 @@ public class TaskbarController { mLauncher = launcher; mTaskbarContainerView = taskbarContainerView; mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks()); + ButtonProvider buttonProvider = new ButtonProvider(launcher); mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view); - mTaskbarViewInApp.construct(createTaskbarViewCallbacks()); + mTaskbarViewInApp.construct(createTaskbarViewCallbacks(), buttonProvider); mTaskbarViewOnHome = taskbarViewOnHome; - mTaskbarViewOnHome.construct(createTaskbarViewCallbacks()); + mTaskbarViewOnHome.construct(createTaskbarViewCallbacks(), buttonProvider); + mImeBarView = mTaskbarContainerView.findViewById(R.id.ime_bar_view); + mImeBarView.construct(buttonProvider); + mNavButtonController = new TaskbarNavButtonController(launcher); mWindowManager = mLauncher.getWindowManager(); mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize); mTaskbarStateHandler = mLauncher.getTaskbarStateHandler(); @@ -108,10 +125,20 @@ public class TaskbarController { @Override public void updateTaskbarVisibilityAlpha(float alpha) { - mTaskbarContainerView.setAlpha(alpha); + mTaskbarViewInApp.setAlpha(alpha); mTaskbarViewOnHome.setAlpha(alpha); } + @Override + public void updateImeBarVisibilityAlpha(float alpha) { + if (mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) { + // TODO Remove sysui IME bar for gesture nav as well + return; + } + mImeBarView.setAlpha(alpha); + mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE); + } + @Override public void updateTaskbarScale(float scale) { mTaskbarViewInApp.setScaleX(scale); @@ -136,16 +163,21 @@ public class TaskbarController { return new TaskbarContainerViewCallbacks() { @Override public void onViewRemoved() { - if (mTaskbarContainerView.getChildCount() == 1) { - // Only TaskbarView remains. - setTaskbarWindowFullscreen(false); + // Ensure no other children present (like Folders, etc) + for (int i = 0; i < mTaskbarContainerView.getChildCount(); i++) { + View v = mTaskbarContainerView.getChildAt(i); + if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))){ + return; + } } + setTaskbarWindowFullscreen(false); } @Override public boolean isTaskbarTouchable() { return mTaskbarContainerView.getAlpha() > AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD - && mTaskbarViewInApp.getVisibility() == View.VISIBLE + && (mTaskbarViewInApp.getVisibility() == VISIBLE + || mImeBarView.getVisibility() == VISIBLE) && !mIsAnimatingToLauncher; } }; @@ -198,7 +230,7 @@ public class TaskbarController { // space so that the others line up with the home screen hotseat. boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher; - return isOnHomeScreen ? View.INVISIBLE : View.GONE; + return isOnHomeScreen ? INVISIBLE : GONE; } @Override @@ -212,6 +244,11 @@ public class TaskbarController { alignRealHotseatWithTaskbar(); } } + + @Override + public void onNavigationButtonClick(@TaskbarButton int buttonType) { + mNavButtonController.onButtonClick(buttonType); + } }; } @@ -228,9 +265,12 @@ public class TaskbarController { * Initializes the Taskbar, including adding it to the screen. */ public void init() { - mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons()); - mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons()); + mNavMode = SysUINavigationMode.INSTANCE.get(mLauncher) + .addModeChangeListener(mNavigationModeChangeListener); + mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(), mNavMode); + mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(), mNavMode); mTaskbarContainerView.init(mTaskbarViewInApp); + mImeBarView.init(createTaskbarViewCallbacks()); addToWindowManager(); mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks()); mTaskbarAnimationController.init(); @@ -272,12 +312,15 @@ public class TaskbarController { mTaskbarViewInApp.cleanup(); mTaskbarViewOnHome.cleanup(); mTaskbarContainerView.cleanup(); + mImeBarView.cleanup(); removeFromWindowManager(); mTaskbarStateHandler.setTaskbarCallbacks(null); mTaskbarAnimationController.cleanup(); mHotseatController.cleanup(); setWhichTaskbarViewIsVisible(null); + SysUINavigationMode.INSTANCE.get(mLauncher) + .removeModeChangeListener(mNavigationModeChangeListener); } private void removeFromWindowManager() { @@ -315,6 +358,12 @@ public class TaskbarController { mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams); } + private void onNavModeChanged(SysUINavigationMode.Mode newMode) { + mNavMode = newMode; + cleanup(); + init(); + } + /** * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. */ @@ -387,6 +436,28 @@ public class TaskbarController { */ public void setIsImeVisible(boolean isImeVisible) { mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1); + blockTaskbarTouchesForIme(isImeVisible); + } + + /** + * 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 + * @param backDisposition Used to determine back button behavior for software keyboard + * See BACK_DISPOSITION_* constants in {@link InputMethodService} + */ + public void updateImeStatus(int displayId, int vis, int backDisposition, + boolean showImeSwitcher) { + if (displayId != mTaskbarContainerView.getContext().getDisplayId() || + mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) { + return; + } + + boolean imeVisible = (vis & InputMethodService.IME_VISIBLE) != 0; + mTaskbarAnimationController.animateToVisibilityForIme(imeVisible ? 0 : 1); + mImeBarView.setImeSwitcherVisibility(showImeSwitcher); + blockTaskbarTouchesForIme(imeVisible); } /** @@ -436,12 +507,17 @@ public class TaskbarController { private void setWhichTaskbarViewIsVisible(@Nullable TaskbarView visibleTaskbar) { mTaskbarViewInApp.setVisibility(visibleTaskbar == mTaskbarViewInApp - ? View.VISIBLE : View.INVISIBLE); + ? VISIBLE : INVISIBLE); mTaskbarViewOnHome.setVisibility(visibleTaskbar == mTaskbarViewOnHome - ? View.VISIBLE : View.INVISIBLE); + ? VISIBLE : INVISIBLE); mLauncher.getHotseat().setIconsAlpha(visibleTaskbar != mTaskbarViewInApp ? 1f : 0f); } + private void blockTaskbarTouchesForIme(boolean block) { + mTaskbarViewOnHome.setTouchesEnabled(!block); + mTaskbarViewInApp.setTouchesEnabled(!block); + } + /** * Returns the ratio of the taskbar icon size on home vs in an app. */ @@ -485,6 +561,7 @@ public class TaskbarController { protected interface TaskbarAnimationControllerCallbacks { void updateTaskbarBackgroundAlpha(float alpha); void updateTaskbarVisibilityAlpha(float alpha); + void updateImeBarVisibilityAlpha(float alpha); void updateTaskbarScale(float scale); void updateTaskbarTranslationY(float translationY); } @@ -507,6 +584,7 @@ public class TaskbarController { /** Returns how much to scale non-icon elements such as spacing and dividers. */ float getNonIconScale(TaskbarView taskbarView); void onItemPositionsChanged(TaskbarView taskbarView); + void onNavigationButtonClick(@TaskbarButton int buttonType); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java new file mode 100644 index 0000000000..54e1610cd1 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -0,0 +1,100 @@ +/* + * 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; + +import android.content.Context; +import android.content.Intent; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.IntDef; + +import com.android.quickstep.OverviewCommandHelper; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TouchInteractionService; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Controller for 3 button mode in the taskbar. + * Handles all the functionality of the various buttons, making/routing the right calls into + * launcher or sysui/system. + * + * TODO: Create callbacks to hook into UI layer since state will change for more context buttons/ + * assistant invocation. + */ +public class TaskbarNavButtonController { + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + BUTTON_BACK, + BUTTON_HOME, + BUTTON_RECENTS, + BUTTON_IME_SWITCH + }) + + public @interface TaskbarButton {} + + static final int BUTTON_BACK = 1; + static final int BUTTON_HOME = BUTTON_BACK << 1; + static final int BUTTON_RECENTS = BUTTON_HOME << 1; + static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; + + + private final Context mContext; + + public TaskbarNavButtonController(Context context) { + mContext = context; + } + + public void onButtonClick(@TaskbarButton int buttonType) { + switch (buttonType) { + case BUTTON_BACK: + executeBack(); + break; + case BUTTON_HOME: + navigateHome(); + break; + case BUTTON_RECENTS: + navigateToOverview();; + break; + case BUTTON_IME_SWITCH: + showIMESwitcher(); + break; + } + } + + private void navigateHome() { + mContext.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + + private void navigateToOverview() { + TouchInteractionService.getInstance().getOverviewCommandHelper() + .addCommand(OverviewCommandHelper.TYPE_SHOW); + } + + private void executeBack() { + SystemUiProxy.INSTANCE.getNoCreate().onBackPressed(); + } + + private void showIMESwitcher() { + mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem( + true /* showAuxiliarySubtypes */, mContext.getDisplayId()); + } + +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 60a7add7fd..9e8013e63e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -15,6 +15,10 @@ */ package com.android.launcher3.taskbar; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; + import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.LayoutTransition; @@ -24,8 +28,10 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; +import android.os.SystemProperties; import android.util.AttributeSet; import android.view.DragEvent; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -45,12 +51,17 @@ 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.views.ActivityContext; +import com.android.quickstep.SysUINavigationMode; /** * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable { + + private static final boolean ENABLE_THREE_BUTTON_TASKBAR = + SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); + private final int mIconTouchSize; private final boolean mIsRtl; private final int mTouchSlop; @@ -68,15 +79,22 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa private LayoutTransition mLayoutTransition; private int mHotseatStartIndex; private int mHotseatEndIndex; + private LinearLayout mButtonRegion; // Delegate touches to the closest view if within mIconTouchSize. private boolean mDelegateTargeted; private View mDelegateView; + // Prevents dispatching touches to children if true + private boolean mTouchEnabled = true; private boolean mIsDraggingItem; // Only non-null when the corresponding Folder is open. private @Nullable FolderIcon mLeaveBehindFolderIcon; + private int mNavButtonStartIndex; + /** Provider of buttons added to taskbar in 3 button nav */ + private ButtonProvider mButtonProvider; + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -100,15 +118,28 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } - protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { + protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks, + ButtonProvider buttonProvider) { mControllerCallbacks = taskbarViewCallbacks; mNonIconScale = mControllerCallbacks.getNonIconScale(this); mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing); mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale); + mButtonProvider = buttonProvider; + mButtonProvider.setMarginLeftRight(mItemMarginLeftRight); } - protected void init(int numHotseatIcons) { - mHotseatStartIndex = 0; + protected void init(int numHotseatIcons, SysUINavigationMode.Mode newMode) { + // TODO: check if buttons on left + if (newMode == SysUINavigationMode.Mode.THREE_BUTTONS && ENABLE_THREE_BUTTON_TASKBAR) { + // 3 button + mNavButtonStartIndex = 0; + createNavButtons(); + } else { + mNavButtonStartIndex = -1; + removeNavButtons(); + } + + mHotseatStartIndex = mNavButtonStartIndex + 1; mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1; updateHotseatItems(new ItemInfo[numHotseatIcons]); @@ -185,11 +216,11 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId || needsReinflate) { removeView(hotseatView); - ActivityContext activityContext = ActivityContext.lookupContext(getContext()); + ActivityContext activityContext = getActivityContext(); if (isFolder) { FolderInfo folderInfo = (FolderInfo) hotseatItemInfo; FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId, - ActivityContext.lookupContext(getContext()), this, folderInfo); + getActivityContext(), this, folderInfo); folderIcon.setTextVisible(false); hotseatView = folderIcon; } else { @@ -243,12 +274,24 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (!mTouchEnabled) { + return true; + } + return super.dispatchTouchEvent(ev); + } + @Override public boolean onTouchEvent(MotionEvent event) { boolean handled = delegateTouchIfNecessary(event); return super.onTouchEvent(event) || handled; } + public void setTouchesEnabled(boolean touchEnabled) { + this.mTouchEnabled = touchEnabled; + } + /** * User touched the Taskbar background. Determine whether the touch is close enough to a view * that we should forward the touches to it. @@ -335,12 +378,57 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null; } + private void removeNavButtons() { + if (mButtonRegion != null) { + mButtonRegion.removeAllViews(); + removeView(mButtonRegion); + } // else We've never been in 3 button. Woah Scoob! + } + + /** + * Add back/home/recents buttons into a single ViewGroup that will be inserted at + * {@param navButtonStartIndex} + */ + private void createNavButtons() { + ActivityContext context = getActivityContext(); + if (mButtonRegion == null) { + mButtonRegion = new LinearLayout(getContext()); + } else { + mButtonRegion.removeAllViews(); + } + mButtonRegion.setVisibility(VISIBLE); + + LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( + context.getDeviceProfile().iconSizePx, + context.getDeviceProfile().iconSizePx + ); + buttonParams.gravity = Gravity.CENTER; + + View backButton = mButtonProvider.getBack(); + backButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( + BUTTON_BACK)); + mButtonRegion.addView(backButton, buttonParams); + + // Home button + View homeButton = mButtonProvider.getHome(); + homeButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( + BUTTON_HOME)); + mButtonRegion.addView(homeButton, buttonParams); + + View recentsButton = mButtonProvider.getRecents(); + recentsButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( + BUTTON_RECENTS)); + mButtonRegion.addView(recentsButton, buttonParams); + + addView(mButtonRegion, mNavButtonStartIndex); + } + @Override public boolean onDragEvent(DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mIsDraggingItem = true; - AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext())); + AbstractFloatingView.closeAllOpenViews(getActivityContext()); return true; case DragEvent.ACTION_DRAG_ENDED: mIsDraggingItem = false; @@ -407,12 +495,15 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } private View inflate(@LayoutRes int layoutResId) { - return ActivityContext.lookupContext(getContext()).getLayoutInflater() - .inflate(layoutResId, this, false); + return getActivityContext().getLayoutInflater().inflate(layoutResId, this, false); } @Override public void setInsets(Rect insets) { // Ignore, we just implement Insettable to draw behind system insets. } + + private T getActivityContext() { + return ActivityContext.lookupContext(getContext()); + } } diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java index efec037b92..74f4bea2a8 100644 --- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java +++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java @@ -34,6 +34,7 @@ import com.android.launcher3.util.MainThreadInitializedObject; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * Observer for the resource config that specifies the navigation bar mode. @@ -75,7 +76,8 @@ public class SysUINavigationMode { private int mNavBarGesturalHeight; private int mNavBarLargerGesturalHeight; - private final List mChangeListeners = new ArrayList<>(); + private final List mChangeListeners = + new CopyOnWriteArrayList<>(); private final List mOneHandedOverlayChangeListeners = new ArrayList<>(); diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 338a6efd82..5fe0fc72a2 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -52,6 +52,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; @@ -145,7 +146,22 @@ public class TouchInteractionService extends Service implements PluginListener mDeviceState.setDeferredGestureRegion(region)); } + @Override public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) { WindowBounds wb = new WindowBounds(bounds, insets); MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb)); } + + @Override + public void onImeWindowStatusChanged(int displayId, IBinder token, int vis, + int backDisposition, boolean showImeSwitcher) throws RemoteException { + if (mTaskbarOverviewProxyDelegate == null) { + return; + } + MAIN_EXECUTOR.execute(() -> { + if (mTaskbarOverviewProxyDelegate == null) { + return; + } + mTaskbarOverviewProxyDelegate + .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher); + }); + } }; + public interface TaskbarOverviewProxyDelegate { + void updateImeStatus(int displayId, int vis, int backDisposition, + boolean showImeSwitcher); + } + private static boolean sConnected = false; + private static TouchInteractionService sInstance; private static boolean sIsInitialized = false; private RotationTouchHelper mRotationTouchHelper; + @Nullable + private TaskbarOverviewProxyDelegate mTaskbarOverviewProxyDelegate; public static boolean isConnected() { return sConnected; } + @Nullable + public static TouchInteractionService getInstance() { + return sInstance; + } + public static boolean isInitialized() { return sIsInitialized; } @@ -293,6 +338,10 @@ public class TouchInteractionService extends Service implements PluginListener