targets) {
+ mPredictorState = predictorState;
+ mTargets = targets;
+ }
+
+ /**
+ * Uses the app predication result to infer widgets that the user may want to use.
+ *
+ * The algorithm uses the app prediction ranking to create a widgets ranking which only
+ * includes one widget per app and excludes widgets that have already been added to the
+ * workspace.
+ */
+ @Override
+ public void execute(LauncherAppState appState, BgDataModel dataModel, AllAppsList apps) {
+ Set widgetsInWorkspace = dataModel.appWidgets.stream().map(
+ widget -> new ComponentKey(widget.providerName, widget.user)).collect(
+ Collectors.toSet());
+ Map> allWidgets =
+ dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
+
+ FixedContainerItems fixedContainerItems = mPredictorState.items;
+ fixedContainerItems.items.clear();
+
+ if (FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER.get()) {
+ for (AppTarget app : mTargets) {
+ PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(),
+ app.getUser());
+ if (allWidgets.containsKey(packageUserKey)) {
+ List notAddedWidgets = allWidgets.get(packageUserKey).stream()
+ .filter(item ->
+ !widgetsInWorkspace.contains(
+ new ComponentKey(item.componentName, item.user)))
+ .collect(Collectors.toList());
+ if (notAddedWidgets.size() > 0) {
+ // Even an apps have more than one widgets, we only include one widget.
+ fixedContainerItems.items.add(
+ new PendingAddWidgetInfo(
+ notAddedWidgets.get(0).widgetInfo,
+ CONTAINER_WIDGETS_PREDICTION));
+ }
+ }
+ }
+ } else {
+ Map widgetItems =
+ allWidgets.values().stream().flatMap(List::stream)
+ .collect(Collectors.toMap(widget -> (ComponentKey) widget,
+ widget -> widget));
+ for (AppTarget app : mTargets) {
+ if (TextUtils.isEmpty(app.getClassName())) {
+ continue;
+ }
+ ComponentKey targetWidget = new ComponentKey(
+ new ComponentName(app.getPackageName(), app.getClassName()), app.getUser());
+ if (widgetItems.containsKey(targetWidget)) {
+ fixedContainerItems.items.add(
+ new PendingAddWidgetInfo(widgetItems.get(
+ targetWidget).widgetInfo,
+ CONTAINER_WIDGETS_PREDICTION));
+ }
+ }
+ }
+ bindExtraContainerItems(fixedContainerItems);
+
+ // Don't store widgets prediction to disk because it is not used frequently.
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
index e302b4f152..4d7cc85ed1 100644
--- a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
+++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
@@ -17,6 +17,7 @@
package com.android.launcher3.proxy;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
@@ -48,19 +49,20 @@ public class ProxyActivityStarter extends Activity {
return;
}
- if (mParams.intent != null) {
- startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
- return;
- } else if (mParams.intentSender != null) {
- try {
+ try {
+ if (mParams.intent != null) {
+ startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
+ return;
+ } else if (mParams.intentSender != null) {
startIntentSenderForResult(mParams.intentSender, mParams.requestCode,
mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues,
mParams.extraFlags,
mParams.options);
return;
- } catch (SendIntentException e) {
- mParams.deliverResult(this, RESULT_CANCELED, null);
}
+ } catch (NullPointerException | ActivityNotFoundException | SecurityException
+ | SendIntentException e) {
+ mParams.deliverResult(this, RESULT_CANCELED, null);
}
finishAndRemoveTask();
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index 13501a452b..1f268cc9d9 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -18,6 +18,7 @@ package com.android.launcher3.statehandlers;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AnimatedFloat.VALUE;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
@@ -30,7 +31,7 @@ import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SystemUiProxy;
/**
- * State handler for animating back button alpha
+ * State handler for animating back button alpha in two-button nav mode.
*/
public class BackButtonAlphaHandler implements StateHandler {
@@ -47,18 +48,11 @@ public class BackButtonAlphaHandler implements StateHandler {
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation animation) {
- if (config.onlyPlayAtomicComponent()) {
+ if (SysUINavigationMode.getMode(mLauncher) != TWO_BUTTONS) {
return;
}
- if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
- // If the nav mode is not gestural, then force back button alpha to be 1
- UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
- BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */);
- return;
- }
-
- mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
+ mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastNavButtonAlpha();
animation.setFloat(mBackAlpha, VALUE,
mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR);
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 2ed6b03f59..d8f8ff5dd4 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -24,8 +24,12 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.os.IBinder;
+import android.os.SystemProperties;
import android.util.FloatProperty;
+import android.view.CrossWindowBlurListeners;
+import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import com.android.launcher3.BaseActivity;
@@ -37,13 +41,11 @@ import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.systemui.shared.system.BlurUtils;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SurfaceControlCompat;
-import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WallpaperManagerCompat;
import app.lawnchair.preferences.BasePreferenceManager;
import app.lawnchair.preferences.PreferenceManager;
+import java.util.function.Consumer;
/**
* Controls blur and wallpaper zoom, for the Launcher surface only.
@@ -94,23 +96,37 @@ public class DepthController implements StateHandler,
@Override
public void onDraw() {
View view = mLauncher.getDragLayer();
- setSurface(new SurfaceControlCompat(view));
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
}
};
+ private final Consumer mCrossWindowBlurListener = new Consumer() {
+ @Override
+ public void accept(Boolean enabled) {
+ mCrossWindowBlursEnabled = enabled;
+ dispatchTransactionSurface(mDepth);
+ }
+ };
+
private final Launcher mLauncher;
/**
* Blur radius when completely zoomed out, in pixels.
*/
private int mMaxBlurRadius;
+ private boolean mCrossWindowBlursEnabled;
private WallpaperManagerCompat mWallpaperManager;
- private SurfaceControlCompat mSurface;
+ private SurfaceControl mSurface;
/**
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
* @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
*/
private float mDepth;
+ /**
+ * If we're launching and app and should not be blurring the screen for performance reasons.
+ */
+ private boolean mBlurDisabledForAppLaunch;
// Workaround for animating the depth when multiwindow mode changes.
private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
@@ -128,6 +144,7 @@ public class DepthController implements StateHandler,
mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
mWallpaperManager = new WallpaperManagerCompat(mLauncher);
}
+
if (Utilities.ATLEAST_R && mLauncher.getRootView() != null && mOnAttachListener == null) {
mOnAttachListener = new View.OnAttachStateChangeListener() {
@Override
@@ -137,13 +154,20 @@ public class DepthController implements StateHandler,
if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
}
+ CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
+ mCrossWindowBlurListener);
}
@Override
public void onViewDetachedFromWindow(View view) {
+ CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
}
};
mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
+ if (mLauncher.getRootView().isAttachedToWindow()) {
+ CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
+ mCrossWindowBlurListener);
+ }
}
if (mDrawerOpacity == null) {
mDrawerOpacity = PreferenceManager.getInstance(mLauncher).getDrawerOpacity();
@@ -166,22 +190,17 @@ public class DepthController implements StateHandler,
/**
* Sets the specified app target surface to apply the blur to.
*/
- public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
- if (target != null) {
- setSurface(target.leash);
- } else {
- setActivityStarted(mLauncher.isStarted());
+ public void setSurface(SurfaceControl surface) {
+ // Set launcher as the SurfaceControl when we don't need an external target anymore.
+ if (surface == null) {
+ ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
+ surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null;
}
- }
- private void setSurface(SurfaceControlCompat surface) {
if (mSurface != surface) {
mSurface = surface;
if (surface != null) {
- setDepth(mDepth);
- } else {
- // If there is no surface, then reset the ratio
- setDepth(0f);
+ dispatchTransactionSurface(mDepth);
}
}
}
@@ -195,15 +214,15 @@ public class DepthController implements StateHandler,
float toDepth = toState.getDepth(mLauncher);
if (Float.compare(mDepth, toDepth) != 0) {
setDepth(toDepth);
+ } else if (toState == LauncherState.OVERVIEW) {
+ dispatchTransactionSurface(mDepth);
}
}
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation animation) {
- if (mSurface == null
- || config.onlyPlayAtomicComponent()
- || config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)
+ if (config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)
|| mIgnoreStateChangesDuringMultiWindowAnimation) {
return;
}
@@ -220,6 +239,19 @@ public class DepthController implements StateHandler,
setDepth(toDepth, true);
}
+ /**
+ * If we're launching an app from the home screen.
+ */
+ public void setIsInLaunchTransition(boolean inLaunchTransition) {
+ boolean blurEnabled = SystemProperties.getBoolean("ro.launcher.blur.appLaunch", true);
+ mBlurDisabledForAppLaunch = inLaunchTransition && !blurEnabled;
+ if (!inLaunchTransition) {
+ // Reset depth at the end of the launch animation, so the wallpaper won't be
+ // zoomed out if an app crashes.
+ setDepth(0f);
+ }
+ }
+
private void setDepth(float depth) {
setDepth(depth, false);
}
@@ -235,33 +267,36 @@ public class DepthController implements StateHandler,
if (Float.compare(mDepth, depthF) == 0 && !force) {
return;
}
+ if (dispatchTransactionSurface(depthF)) {
+ mDepth = depthF;
+ }
+ }
+ private boolean dispatchTransactionSurface(float depth) {
boolean supportsBlur = BlurUtils.supportsBlursOnWindows();
if (supportsBlur && (mSurface == null || !mSurface.isValid())) {
- return;
+ return false;
}
- mDepth = depthF;
ensureDependencies();
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
- mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
}
if (supportsBlur) {
- final int blur;
- boolean isDrawerSolid = mDrawerOpacity.get() == 1f;
- if (isDrawerSolid && mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) {
- // All apps has a solid background. We don't need to draw blurs after it's fully
- // visible. This will take us out of GPU composition, saving battery and increasing
- // performance.
- blur = 0;
- } else {
- blur = (int) (mDepth * mMaxBlurRadius);
- }
- new TransactionCompat()
+ // We cannot mark the window as opaque in overview because there will be an app window
+ // below the launcher layer, and we need to draw it -- without blurs.
+ boolean isOverview = mLauncher.isInState(LauncherState.OVERVIEW);
+ boolean opaque = mLauncher.getScrimView().isFullyOpaque() && !isOverview;
+
+ int blur = opaque || isOverview || !mCrossWindowBlursEnabled
+ || mBlurDisabledForAppLaunch ? 0 : (int) (depth * mMaxBlurRadius);
+ new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, blur)
+ .setOpaque(mSurface, opaque)
.apply();
}
+ return true;
}
@Override
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..540f748313
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
@@ -0,0 +1,80 @@
+/*
+ * 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_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+
+import android.annotation.DrawableRes;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+
+/**
+ * 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 final int mMarginLeftRight;
+ private final TaskbarActivityContext mContext;
+
+ public ButtonProvider(TaskbarActivityContext context) {
+ mContext = context;
+ mMarginLeftRight = context.getResources()
+ .getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ }
+
+ public View getBack() {
+ // Back button
+ return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
+ }
+
+ public View getDown() {
+ // Ime down button
+ return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
+ }
+
+ public View getHome() {
+ // Home button
+ return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
+ }
+
+ public View getRecents() {
+ // Recents button
+ return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
+ }
+
+ public View getImeSwitcher() {
+ // IME Switcher Button
+ return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
+ }
+
+ private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
+ ImageView buttonView = new ImageView(mContext);
+ buttonView.setImageResource(drawableId);
+ buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
+ buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
+ buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType));
+ 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..287caab44b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
@@ -0,0 +1,77 @@
+/*
+ * 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.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 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 init(ButtonProvider buttonProvider) {
+ mButtonProvider = buttonProvider;
+
+ 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.setLayoutParams(downParams);
+ downView.setRotation(-90);
+ addView(downView);
+
+ // IME switcher button
+ mImeView = mButtonProvider.getImeSwitcher();
+ mImeView.setLayoutParams(imeParams);
+ addView(mImeView);
+ }
+
+ 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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
new file mode 100644
index 0000000000..c2d107c22d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
+import com.android.launcher3.R;
+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
+ */
+
+public class LauncherTaskbarUIController extends TaskbarUIController {
+
+ private final BaseQuickstepLauncher mLauncher;
+ private final TaskbarStateHandler mTaskbarStateHandler;
+ private final TaskbarAnimationController mTaskbarAnimationController;
+ private final TaskbarHotseatController mHotseatController;
+
+ private final TaskbarActivityContext mContext;
+ final TaskbarDragLayer mTaskbarDragLayer;
+ final TaskbarView mTaskbarView;
+
+ private @Nullable Animator mAnimator;
+ private boolean mIsAnimatingToLauncher;
+
+ public LauncherTaskbarUIController(
+ BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
+ mContext = context;
+ mTaskbarDragLayer = context.getDragLayer();
+ mTaskbarView = mTaskbarDragLayer.findViewById(R.id.taskbar_view);
+
+ mLauncher = launcher;
+ mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
+ mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
+ createTaskbarAnimationControllerCallbacks());
+ mHotseatController = new TaskbarHotseatController(
+ mLauncher, mTaskbarView::updateHotseatItems);
+ }
+
+ @Override
+ protected void onCreate() {
+ mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
+ mTaskbarAnimationController.init();
+ mHotseatController.init();
+ setTaskbarViewVisible(!mLauncher.hasBeenResumed());
+ alignRealHotseatWithTaskbar();
+ mLauncher.setTaskbarUIController(this);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mAnimator != null) {
+ // End this first, in case it relies on properties that are about to be cleaned up.
+ mAnimator.end();
+ }
+ mTaskbarStateHandler.setAnimationController(null);
+ mTaskbarAnimationController.cleanup();
+ mHotseatController.cleanup();
+ setTaskbarViewVisible(true);
+ mLauncher.getHotseat().setIconsAlpha(1f);
+ mLauncher.setTaskbarUIController(null);
+ }
+
+ @Override
+ protected boolean isTaskbarTouchable() {
+ return !mIsAnimatingToLauncher;
+ }
+
+ private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
+ return new TaskbarAnimationControllerCallbacks() {
+ @Override
+ public void updateTaskbarBackgroundAlpha(float alpha) {
+ mTaskbarDragLayer.setTaskbarBackgroundAlpha(alpha);
+ }
+
+ @Override
+ public void updateTaskbarVisibilityAlpha(float alpha) {
+ mTaskbarView.setAlpha(alpha);
+ }
+
+ @Override
+ public void updateImeBarVisibilityAlpha(float alpha) {
+ mTaskbarDragLayer.updateImeBarVisibilityAlpha(alpha);
+ }
+
+ @Override
+ public void updateTaskbarScale(float scale) {
+ mTaskbarView.setScaleX(scale);
+ mTaskbarView.setScaleY(scale);
+ }
+
+ @Override
+ public void updateTaskbarTranslationY(float translationY) {
+ if (translationY < 0) {
+ // Resize to accommodate the max translation we'll reach.
+ mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize
+ + mLauncher.getHotseat().getTaskbarOffsetY());
+ } else {
+ mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
+ }
+ mTaskbarView.setTranslationY(translationY);
+ }
+ };
+ }
+
+ /**
+ * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
+ */
+ public void onLauncherResumedOrPaused(boolean isResumed) {
+ long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ if (isResumed) {
+ mAnimator = createAnimToLauncher(null, duration);
+ } else {
+ mAnimator = createAnimToApp(duration);
+ }
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
+ mAnimator.start();
+ }
+
+ /**
+ * Create Taskbar animation when going from an app to Launcher.
+ * @param toState If known, the state we will end up in when reaching Launcher.
+ */
+ public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
+ PendingAnimation anim = new PendingAnimation(duration);
+ anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
+ if (toState != null) {
+ mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
+ }
+
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingToLauncher = true;
+ mTaskbarView.setHolesAllowedInLayout(true);
+ mTaskbarView.updateHotseatItemsVisibility();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimatingToLauncher = false;
+ setTaskbarViewVisible(false);
+ }
+ });
+
+ return anim.buildAnim();
+ }
+
+ private Animator createAnimToApp(long duration) {
+ PendingAnimation anim = new PendingAnimation(duration);
+ anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTaskbarView.updateHotseatItemsVisibility();
+ setTaskbarViewVisible(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTaskbarView.setHolesAllowedInLayout(false);
+ }
+ });
+ return anim.buildAnim();
+ }
+
+ @Override
+ protected void onImeVisible(TaskbarDragLayer containerView, boolean isVisible) {
+ mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
+ }
+
+ /**
+ * Should be called when one or more items in the Hotseat have changed.
+ */
+ public void onHotseatUpdated() {
+ mHotseatController.onHotseatUpdated();
+ }
+
+ /**
+ * @param ev MotionEvent in screen coordinates.
+ * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
+ */
+ public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
+ return mTaskbarView.isEventOverAnyItem(ev);
+ }
+
+ public boolean isDraggingItem() {
+ return mTaskbarView.isDraggingItem();
+ }
+
+ /**
+ * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
+ */
+ @Override
+ public void alignRealHotseatWithTaskbar() {
+ Rect hotseatBounds = new Rect();
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
+ int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
+ int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
+ int hotseatBottomDiff = taskbarOffset;
+
+ RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
+ Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
+ mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
+ hotseatBoundsF.round(hotseatBounds);
+ mLauncher.getHotseat().setPadding(hotseatBounds.left,
+ hotseatBounds.top + hotseatTopDiff,
+ mTaskbarView.getWidth() - hotseatBounds.right,
+ mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
+ }
+
+ /**
+ * Returns the ratio of the taskbar icon size on home vs in an app.
+ */
+ public float getTaskbarScaleOnHome() {
+ DeviceProfile inAppDp = mContext.getDeviceProfile();
+ DeviceProfile onHomeDp = mLauncher.getDeviceProfile();
+ return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
+ }
+
+ void setTaskbarViewVisible(boolean isVisible) {
+ mTaskbarView.setIconsVisibility(isVisible);
+ mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
+ }
+
+ /**
+ * Contains methods that TaskbarAnimationController can call to interface with
+ * TaskbarController.
+ */
+ protected interface TaskbarAnimationControllerCallbacks {
+ void updateTaskbarBackgroundAlpha(float alpha);
+ void updateTaskbarVisibilityAlpha(float alpha);
+ void updateImeBarVisibilityAlpha(float alpha);
+ void updateTaskbarScale(float scale);
+ void updateTaskbarTranslationY(float translationY);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
new file mode 100644
index 0000000000..4ba0ee0d41
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+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;
+
+import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
+
+import android.app.ActivityOptions;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.folder.Folder;
+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.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+/**
+ * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
+ * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
+ * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
+ */
+public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext {
+
+ private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
+ SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
+ private static final String TAG = "TaskbarActivityContext";
+
+ private static final String WINDOW_TITLE = "Taskbar";
+
+ private final DeviceProfile mDeviceProfile;
+ private final LayoutInflater mLayoutInflater;
+ private final TaskbarDragLayer mDragLayer;
+ private final TaskbarIconController mIconController;
+ private final MyDragController mDragController;
+
+ private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ private final SysUINavigationMode.Mode mNavMode;
+ private final TaskbarNavButtonController mNavButtonController;
+
+ private final boolean mIsSafeModeEnabled;
+
+ @NonNull
+ private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
+
+ private final View.OnClickListener mOnTaskbarIconClickListener;
+ private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
+
+ public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
+ TaskbarNavButtonController buttonController) {
+ super(windowContext, Themes.getActivityThemeRes(windowContext));
+ mDeviceProfile = dp;
+ mNavButtonController = buttonController;
+ mNavMode = SysUINavigationMode.getMode(windowContext);
+ mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
+ () -> getPackageManager().isSafeMode());
+
+ mOnTaskbarIconLongClickListener =
+ new TaskbarDragController(this)::startSystemDragOnLongClick;
+ mOnTaskbarIconClickListener = this::onTaskbarIconClicked;
+
+ float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+ float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+ mDeviceProfile.updateIconSize(iconScale, getResources());
+
+ mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+ mDragLayer = (TaskbarDragLayer) mLayoutInflater
+ .inflate(R.layout.taskbar, null, false);
+ mIconController = new TaskbarIconController(this, mDragLayer);
+ mDragController = new MyDragController(this);
+
+ Display display = windowContext.getDisplay();
+ Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
+ ? windowContext.getApplicationContext()
+ : windowContext.getApplicationContext().createDisplayContext(display);
+ mWindowManager = c.getSystemService(WindowManager.class);
+ }
+
+ public void init() {
+ mWindowLayoutParams = new WindowManager.LayoutParams(
+ MATCH_PARENT,
+ mDeviceProfile.taskbarSize,
+ TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ mWindowLayoutParams.setTitle(WINDOW_TITLE);
+ mWindowLayoutParams.packageName = getPackageName();
+ mWindowLayoutParams.gravity = Gravity.BOTTOM;
+ mWindowLayoutParams.setFitInsetsTypes(0);
+ mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setSystemApplicationOverlay(true);
+
+ WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
+ wmWrapper.setProvidesInsetsTypes(
+ mWindowLayoutParams,
+ new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
+ );
+
+ mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
+ mWindowManager.addView(mDragLayer, mWindowLayoutParams);
+ }
+
+ /**
+ * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
+ */
+ public void setTaskbarWindowHeight(int height) {
+ if (mWindowLayoutParams.height == height) {
+ return;
+ }
+ mWindowLayoutParams.height = height;
+ mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+ }
+
+ public boolean canShowNavButtons() {
+ return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
+ }
+
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ return mLayoutInflater;
+ }
+
+ @Override
+ public TaskbarDragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ @Override
+ public DeviceProfile getDeviceProfile() {
+ return mDeviceProfile;
+ }
+
+ @Override
+ public Rect getFolderBoundingBox() {
+ return mDragLayer.getFolderBoundingBox();
+ }
+
+ @Override
+ public DragController getDragController() {
+ return mDragController;
+ }
+
+ /**
+ * Sets a new data-source for this taskbar instance
+ */
+ public void setUIController(@NonNull TaskbarUIController uiController) {
+ mUIController.onDestroy();
+ mUIController = uiController;
+ mIconController.setUIController(mUIController);
+ mUIController.onCreate();
+ }
+
+ /**
+ * Called when this instance of taskbar is no longer needed
+ */
+ public void onDestroy() {
+ setUIController(TaskbarUIController.DEFAULT);
+ mIconController.onDestroy();
+ mWindowManager.removeViewImmediate(mDragLayer);
+ }
+
+ void onNavigationButtonClick(@TaskbarButton int buttonType) {
+ mNavButtonController.onButtonClick(buttonType);
+ }
+
+ /**
+ * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
+ */
+ public void setImeIsVisible(boolean isImeVisible) {
+ mIconController.setImeIsVisible(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
+ */
+ public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
+ mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
+ }
+
+ /**
+ * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
+ */
+ protected void setTaskbarWindowFullscreen(boolean fullscreen) {
+ setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize);
+ }
+
+ protected void onTaskbarIconClicked(View view) {
+ Object tag = view.getTag();
+ if (tag instanceof Task) {
+ Task task = (Task) tag;
+ ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
+ ActivityOptions.makeBasic());
+ } else if (tag instanceof FolderInfo) {
+ FolderIcon folderIcon = (FolderIcon) view;
+ Folder folder = folderIcon.getFolder();
+ setTaskbarWindowFullscreen(true);
+
+ getDragLayer().post(() -> {
+ folder.animateOpen();
+
+ folder.iterateOverItems((itemInfo, itemView) -> {
+ itemView.setOnClickListener(mOnTaskbarIconClickListener);
+ itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener);
+ // To play haptic when dragging, like other Taskbar items do.
+ itemView.setHapticFeedbackEnabled(true);
+ return false;
+ });
+ });
+ } else if (tag instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
+ if (!(info.isDisabled() && ItemClickHandler.handleDisabledItemClicked(info, this))) {
+ Intent intent = new Intent(info.getIntent())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
+ Toast.makeText(this, R.string.safemode_shortcut_error,
+ Toast.LENGTH_SHORT).show();
+ } else if (info.isPromise()) {
+ intent = new PackageManagerHelper(this)
+ .getMarketIntent(info.getTargetPackage())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+
+ } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ String id = info.getDeepShortcutId();
+ String packageName = intent.getPackage();
+ getSystemService(LauncherApps.class)
+ .startShortcut(packageName, id, null, null, info.user);
+ } else if (info.user.equals(Process.myUserHandle())) {
+ startActivity(intent);
+ } else {
+ getSystemService(LauncherApps.class).startMainActivity(
+ intent.getComponent(), info.user, intent.getSourceBounds(), null);
+ }
+ } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
+ .show();
+ Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
+ }
+ }
+ } else {
+ Log.e(TAG, "Unknown type clicked: " + tag);
+ }
+
+ AbstractFloatingView.closeAllOpenViews(this);
+ }
+
+ private static class MyDragController extends DragController {
+ MyDragController(TaskbarActivityContext activity) {
+ super(activity);
+ }
+
+ @Override
+ protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
+ DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
+ ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
+ float dragViewScaleOnDrop, DragOptions options) {
+ return null;
+ }
+
+ @Override
+ protected void exitDrag() {
+ }
+
+ @Override
+ protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
+ return null;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
new file mode 100644
index 0000000000..e20ddf88ce
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherState.TASKBAR;
+
+import android.animation.Animator;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.QuickStepContract;
+
+/**
+ * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
+ * as LauncherState, whether Launcher is in the foreground, etc.
+ */
+public class TaskbarAnimationController {
+
+ private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
+
+ private final BaseQuickstepLauncher mLauncher;
+ private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
+
+ // Background alpha.
+ private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
+ this::onTaskbarBackgroundAlphaChanged);
+
+ // Overall visibility.
+ private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
+ this::updateVisibilityAlpha);
+ private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
+ this::updateVisibilityAlphaForIme);
+
+ // Scale.
+ private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
+ this::updateScale);
+
+ // TranslationY.
+ private final AnimatedFloat mTaskbarTranslationYForLauncherState = new AnimatedFloat(
+ this::updateTranslationY);
+
+ public TaskbarAnimationController(BaseQuickstepLauncher launcher,
+ TaskbarAnimationControllerCallbacks taskbarCallbacks) {
+ mLauncher = launcher;
+ mTaskbarCallbacks = taskbarCallbacks;
+ }
+
+ protected void init() {
+ mTaskbarBackgroundAlpha.updateValue(mLauncher.hasBeenResumed() ? 0f : 1f);
+ boolean isVisibleForLauncherState = (mLauncher.getStateManager().getState()
+ .getVisibleElements(mLauncher) & TASKBAR) != 0;
+ mTaskbarVisibilityAlphaForLauncherState.updateValue(isVisibleForLauncherState ? 1f : 0f);
+ boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
+ & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
+ mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
+
+ onTaskbarBackgroundAlphaChanged();
+ updateVisibilityAlpha();
+ }
+
+ protected void cleanup() {
+ setNavBarButtonAlpha(1f);
+ }
+
+ protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
+ return mTaskbarVisibilityAlphaForLauncherState;
+ }
+
+ protected AnimatedFloat getTaskbarScaleForLauncherState() {
+ return mTaskbarScaleForLauncherState;
+ }
+
+ protected AnimatedFloat getTaskbarTranslationYForLauncherState() {
+ return mTaskbarTranslationYForLauncherState;
+ }
+
+ protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
+ return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
+ .setDuration(duration);
+ }
+
+ protected void animateToVisibilityForIme(float toAlpha) {
+ mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForIme.value, toAlpha)
+ .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
+ }
+
+ private void onTaskbarBackgroundAlphaChanged() {
+ mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
+ updateVisibilityAlpha();
+ updateScale();
+ updateTranslationY();
+ }
+
+ private void updateVisibilityAlpha() {
+ // 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 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
+ // LauncherState if Launcher is paused.
+ float scale = mTaskbarScaleForLauncherState.value;
+ scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
+ mTaskbarCallbacks.updateTaskbarScale(scale);
+ }
+
+ private void updateTranslationY() {
+ // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
+ // assumption being that Taskbar should always be at translationY 0f regardless of the
+ // current LauncherState if Launcher is paused.
+ float translationY = mTaskbarTranslationYForLauncherState.value;
+ translationY = Utilities.mapRange(mTaskbarBackgroundAlpha.value, translationY, 0f);
+ mTaskbarCallbacks.updateTaskbarTranslationY(translationY);
+ }
+
+ private void setNavBarButtonAlpha(float navBarAlpha) {
+ SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
new file mode 100644
index 0000000000..ee44927ce4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.os.UserHandle;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ClipDescriptionCompat;
+import com.android.systemui.shared.system.LauncherAppsCompat;
+
+/**
+ * Handles long click on Taskbar items to start a system drag and drop operation.
+ */
+public class TaskbarDragController {
+
+ private final Context mContext;
+ private final int mDragIconSize;
+
+ public TaskbarDragController(Context context) {
+ mContext = context;
+ Resources resources = mContext.getResources();
+ mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
+ }
+
+ /**
+ * Attempts to start a system drag and drop operation for the given View, using its tag to
+ * generate the ClipDescription and Intent.
+ * @return Whether {@link View#startDragAndDrop} started successfully.
+ */
+ protected boolean startSystemDragOnLongClick(View view) {
+ if (!(view instanceof BubbleTextView)) {
+ return false;
+ }
+
+ BubbleTextView btv = (BubbleTextView) view;
+ View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+ @Override
+ public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+ shadowSize.set(mDragIconSize, mDragIconSize);
+ // TODO: should be based on last touch point on the icon.
+ shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
+ }
+
+ @Override
+ public void onDrawShadow(Canvas canvas) {
+ canvas.save();
+ float scale = (float) mDragIconSize / btv.getIconSize();
+ canvas.scale(scale, scale);
+ btv.getIcon().draw(canvas);
+ canvas.restore();
+ }
+ };
+
+ Object tag = view.getTag();
+ ClipDescription clipDescription = null;
+ Intent intent = null;
+ if (tag instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ clipDescription = new ClipDescription(item.title,
+ new String[] {
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ ? ClipDescriptionCompat.MIMETYPE_APPLICATION_SHORTCUT
+ : ClipDescriptionCompat.MIMETYPE_APPLICATION_ACTIVITY
+ });
+ intent = new Intent();
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ID, item.getDeepShortcutId());
+ } else {
+ intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT,
+ LauncherAppsCompat.getMainActivityLaunchIntent(launcherApps,
+ item.getIntent().getComponent(), null, item.user));
+ }
+ intent.putExtra(Intent.EXTRA_USER, item.user);
+ } else if (tag instanceof Task) {
+ Task task = (Task) tag;
+ clipDescription = new ClipDescription(task.titleDescription,
+ new String[] {
+ ClipDescriptionCompat.MIMETYPE_APPLICATION_TASK
+ });
+ intent = new Intent();
+ intent.putExtra(ClipDescriptionCompat.EXTRA_TASK_ID, task.key.id);
+ intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId));
+ }
+
+ if (clipDescription != null && intent != null) {
+ ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
+ view.setOnDragListener(getDraggedViewDragListener());
+ return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
+ }
+ return false;
+ }
+
+ /**
+ * Hide the original Taskbar item while it is being dragged.
+ */
+ private View.OnDragListener getDraggedViewDragListener() {
+ return (view, dragEvent) -> {
+ switch (dragEvent.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ view.setVisibility(INVISIBLE);
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ view.setVisibility(VISIBLE);
+ view.setOnDragListener(null);
+ return true;
+ }
+ return false;
+ };
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
new file mode 100644
index 0000000000..45ec911527
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
+
+/**
+ * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
+ */
+public class TaskbarDragLayer extends BaseDragLayer {
+
+ private final int mFolderMargin;
+ private final Paint mTaskbarBackgroundPaint;
+
+ private TaskbarIconController.Callbacks mControllerCallbacks;
+ private TaskbarView mTaskbarView;
+
+ private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+
+ public TaskbarDragLayer(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, 1 /* alphaChannelCount */);
+ mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+ mTaskbarBackgroundPaint = new Paint();
+ mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
+ recreateControllers();
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[0];
+ }
+
+ public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
+ mControllerCallbacks = callbacks;
+ mTaskbarView = taskbarView;
+ }
+
+ private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
+ if (mControllerCallbacks != null) {
+ mControllerCallbacks.updateInsetsTouchability(insetsInfo);
+ }
+ }
+
+ protected void onDestroy() {
+ ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
+ mTaskbarInsetsComputer);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ onDestroy();
+ }
+
+ @Override
+ protected boolean canFindActiveController() {
+ // Unlike super class, we want to be able to find controllers when touches occur in the
+ // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
+ return true;
+ }
+
+ public void updateImeBarVisibilityAlpha(float alpha) {
+ if (mControllerCallbacks != null) {
+ mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
+ }
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ if (mControllerCallbacks != null) {
+ mControllerCallbacks.onDragLayerViewRemoved();
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
+ canvas.getHeight(), mTaskbarBackgroundPaint);
+ super.dispatchDraw(canvas);
+ }
+
+ /**
+ * @return Bounds (in our coordinates) where an opened Folder can display.
+ */
+ protected Rect getFolderBoundingBox() {
+ Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
+ boundingBox.inset(mFolderMargin, mFolderMargin);
+ return boundingBox;
+ }
+
+
+ /**
+ * Sets the alpha of the background color behind all the Taskbar contents.
+ * @param alpha 0 is fully transparent, 1 is fully opaque.
+ */
+ protected void setTaskbarBackgroundAlpha(float alpha) {
+ mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
+ invalidate();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
new file mode 100644
index 0000000000..91cf7efab5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.ItemInfo;
+
+import java.util.function.Consumer;
+
+/**
+ * Works with TaskbarController to update the TaskbarView's Hotseat items.
+ */
+public class TaskbarHotseatController {
+
+ private final BaseQuickstepLauncher mLauncher;
+ private final Hotseat mHotseat;
+ private final Consumer mTaskbarCallbacks;
+ private final int mNumHotseatIcons;
+
+ private final DragController.DragListener mDragListener = new DragController.DragListener() {
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
+
+ @Override
+ public void onDragEnd() {
+ onHotseatUpdated();
+ }
+ };
+
+ public TaskbarHotseatController(
+ BaseQuickstepLauncher launcher, Consumer taskbarCallbacks) {
+ mLauncher = launcher;
+ mHotseat = mLauncher.getHotseat();
+ mTaskbarCallbacks = taskbarCallbacks;
+ mNumHotseatIcons = mLauncher.getDeviceProfile().numShownHotseatIcons;
+ }
+
+ protected void init() {
+ mLauncher.getDragController().addDragListener(mDragListener);
+ onHotseatUpdated();
+ }
+
+ protected void cleanup() {
+ mLauncher.getDragController().removeDragListener(mDragListener);
+ }
+
+ /**
+ * Called when any Hotseat item changes, and reports the new list of items to TaskbarController.
+ */
+ protected void onHotseatUpdated() {
+ ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets();
+ ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons];
+ for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) {
+ View child = shortcutsAndWidgets.getChildAt(i);
+ Object tag = shortcutsAndWidgets.getChildAt(i).getTag();
+ if (tag instanceof ItemInfo) {
+ ItemInfo itemInfo = (ItemInfo) tag;
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ // Since the hotseat might be laid out vertically or horizontally, use whichever
+ // index is higher.
+ int index = Math.max(lp.cellX, lp.cellY);
+ if (0 <= index && index < hotseatItemInfos.length) {
+ hotseatItemInfos[index] = itemInfo;
+ }
+ }
+ }
+
+ mTaskbarCallbacks.accept(hotseatItemInfos);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
new file mode 100644
index 0000000000..683a5b9fc4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
+
+/**
+ * Controller for taskbar icon UI
+ */
+public class TaskbarIconController {
+
+ private final Rect mTempRect = new Rect();
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarDragLayer mDragLayer;
+
+ private final TaskbarView mTaskbarView;
+ private final ImeBarView mImeBarView;
+
+ @NonNull
+ private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
+
+ TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
+ mActivity = activity;
+ mDragLayer = dragLayer;
+ mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
+ mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
+ }
+
+ public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
+ mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
+ mUIController.alignRealHotseatWithTaskbar());
+
+ ButtonProvider buttonProvider = new ButtonProvider(mActivity);
+ mImeBarView.init(buttonProvider);
+ mTaskbarView.construct(clickListener, longClickListener, buttonProvider);
+ mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+ mDragLayer.init(new Callbacks(), mTaskbarView);
+ }
+
+ public void onDestroy() {
+ mDragLayer.onDestroy();
+ }
+
+ public void setUIController(@NonNull TaskbarUIController uiController) {
+ mUIController = uiController;
+ }
+
+ /**
+ * 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) {
+ if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) {
+ return;
+ }
+
+ mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
+ setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0);
+ }
+
+ /**
+ * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
+ */
+ public void setImeIsVisible(boolean isImeVisible) {
+ mTaskbarView.setTouchesEnabled(!isImeVisible);
+ mUIController.onImeVisible(mDragLayer, isImeVisible);
+ }
+
+ /**
+ * Callbacks for {@link TaskbarDragLayer} to interact with the icon controller
+ */
+ public class Callbacks {
+
+ /**
+ * Called to update the touchable insets
+ */
+ public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+ insetsInfo.touchableRegion.setEmpty();
+ if (mDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (mImeBarView.getVisibility() == VISIBLE) {
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ } else if (!mUIController.isTaskbarTouchable()) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (mTaskbarView.areIconsVisible()) {
+ // Buttons are visible, take over the full taskbar area
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ } else {
+ if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) {
+ mDragLayer.getDescendantRectRelativeToSelf(
+ mTaskbarView.mSystemButtonContainer, mTempRect);
+ insetsInfo.touchableRegion.set(mTempRect);
+ }
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+
+ // TaskbarContainerView provides insets to other apps based on contentInsets. These
+ // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
+ // to show a floating view like Folder. Thus, we set the contentInsets to be where
+ // mTaskbarView is, since its position never changes and insets rather than overlays.
+ insetsInfo.contentInsets.left = mTaskbarView.getLeft();
+ insetsInfo.contentInsets.top = mTaskbarView.getTop();
+ insetsInfo.contentInsets.right = mDragLayer.getWidth() - mTaskbarView.getRight();
+ insetsInfo.contentInsets.bottom = mDragLayer.getHeight() - mTaskbarView.getBottom();
+ }
+
+ public void onDragLayerViewRemoved() {
+ int count = mDragLayer.getChildCount();
+ // Ensure no other children present (like Folders, etc)
+ for (int i = 0; i < count; i++) {
+ View v = mDragLayer.getChildAt(i);
+ if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) {
+ return;
+ }
+ }
+ mActivity.setTaskbarWindowFullscreen(false);
+ }
+
+ public void updateImeBarVisibilityAlpha(float alpha) {
+ if (!mActivity.canShowNavButtons()) {
+ // TODO Remove sysui IME bar for gesture nav as well
+ return;
+ }
+ mImeBarView.setAlpha(alpha);
+ mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
new file mode 100644
index 0000000000..d026bfbf37
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
+import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.inputmethodservice.InputMethodService;
+import android.view.Display;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.TouchInteractionService;
+
+/**
+ * Class to manager taskbar lifecycle
+ */
+public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
+ SysUINavigationMode.NavigationModeChangeListener {
+
+ private final Context mContext;
+ private final DisplayController mDisplayController;
+ private final SysUINavigationMode mSysUINavigationMode;
+ private final TaskbarNavButtonController mNavButtonController;
+
+ private TaskbarActivityContext mTaskbarActivityContext;
+ private BaseQuickstepLauncher mLauncher;
+
+ private static final int CHANGE_FLAGS =
+ CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
+
+ private boolean mUserUnlocked = false;
+
+ public TaskbarManager(TouchInteractionService service) {
+ mDisplayController = DisplayController.INSTANCE.get(service);
+ mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service);
+ Display display =
+ service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null);
+ mNavButtonController = new TaskbarNavButtonController(service);
+
+ mDisplayController.addChangeListener(this);
+ mSysUINavigationMode.addModeChangeListener(this);
+ recreateTaskbar();
+ }
+
+ @Override
+ public void onNavigationModeChanged(Mode newMode) {
+ recreateTaskbar();
+ }
+
+ @Override
+ public void onDisplayInfoChanged(Context context, Info info, int flags) {
+ if ((flags & CHANGE_FLAGS) != 0) {
+ recreateTaskbar();
+ }
+ }
+
+ private void destroyExistingTaskbar() {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onDestroy();
+ mTaskbarActivityContext = null;
+ }
+ }
+
+ /**
+ * Called when the user is unlocked
+ */
+ public void onUserUnlocked() {
+ mUserUnlocked = true;
+ recreateTaskbar();
+ }
+
+ /**
+ * Sets or clears a launcher to act as taskbar callback
+ */
+ public void setLauncher(@Nullable BaseQuickstepLauncher launcher) {
+ mLauncher = launcher;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setUIController(mLauncher == null
+ ? TaskbarUIController.DEFAULT
+ : new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+ }
+ }
+
+ private void recreateTaskbar() {
+ destroyExistingTaskbar();
+ if (!FeatureFlags.ENABLE_TASKBAR.get()) {
+ return;
+ }
+ if (!mUserUnlocked) {
+ return;
+ }
+ DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
+ if (!dp.isTaskbarPresent) {
+ return;
+ }
+ mTaskbarActivityContext = new TaskbarActivityContext(
+ mContext, dp.copy(mContext), mNavButtonController);
+ mTaskbarActivityContext.init();
+ if (mLauncher != null) {
+ mTaskbarActivityContext.setUIController(
+ new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext));
+ }
+ }
+
+ /**
+ * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
+ * @param systemUiStateFlags The latest SystemUiStateFlags
+ */
+ public void onSystemUiFlagsChanged(int systemUiStateFlags) {
+ boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setImeIsVisible(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 (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
+ }
+ }
+
+ /**
+ * Called when the manager is no longer needed
+ */
+ public void destroy() {
+ destroyExistingTaskbar();
+ mDisplayController.removeChangeListener(this);
+ mSysUINavigationMode.removeModeChangeListener(this);
+ }
+}
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..3b5afad691
--- /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 static android.view.Display.DEFAULT_DISPLAY;
+
+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 TouchInteractionService mService;
+
+ public TaskbarNavButtonController(TouchInteractionService service) {
+ mService = service;
+ }
+
+ 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() {
+ mService.startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ private void navigateToOverview() {
+ mService.getOverviewCommandHelper()
+ .addCommand(OverviewCommandHelper.TYPE_SHOW);
+ }
+
+ private void executeBack() {
+ SystemUiProxy.INSTANCE.getNoCreate().onBackPressed();
+ }
+
+ private void showIMESwitcher() {
+ mService.getSystemService(InputMethodManager.class)
+ .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
+ DEFAULT_DISPLAY);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
new file mode 100644
index 0000000000..a701aae088
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherState.TASKBAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
+ * isn't present (i.e. {@link #setAnimationController} is never called).
+ */
+public class TaskbarStateHandler implements StateManager.StateHandler {
+
+ private final BaseQuickstepLauncher mLauncher;
+
+ // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything.
+ private @Nullable TaskbarAnimationController mAnimationController = null;
+
+ public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
+ mLauncher = launcher;
+ }
+
+ public void setAnimationController(TaskbarAnimationController callbacks) {
+ mAnimationController = callbacks;
+ }
+
+ @Override
+ public void setState(LauncherState state) {
+ setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
+ }
+
+ @Override
+ public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+ PendingAnimation animation) {
+ setState(toState, animation);
+ }
+
+ private void setState(LauncherState toState, PropertySetter setter) {
+ if (mAnimationController == null) {
+ return;
+ }
+
+ boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
+ setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(),
+ AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
+ setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(),
+ AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR);
+ setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(),
+ AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
new file mode 100644
index 0000000000..50adeadbfc
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+/**
+ * Base class for providing different taskbar UI
+ */
+public class TaskbarUIController {
+
+ public static final TaskbarUIController DEFAULT = new TaskbarUIController();
+
+ /**
+ * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
+ */
+ public void alignRealHotseatWithTaskbar() { }
+
+ protected void onCreate() { }
+
+ protected void onDestroy() { }
+
+ protected boolean isTaskbarTouchable() {
+ return true;
+ }
+
+ protected void onImeVisible(TaskbarDragLayer container, boolean isVisible) {
+ container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
new file mode 100644
index 0000000000..c6573a639c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.DragEvent;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.LinearLayout;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+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.views.ActivityContext;
+
+/**
+ * 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 final int mIconTouchSize;
+ private final boolean mIsRtl;
+ private final int mTouchSlop;
+ private final RectF mTempDelegateBounds = new RectF();
+ private final RectF mDelegateSlopBounds = new RectF();
+ private final int[] mTempOutLocation = new int[2];
+
+ private final int mItemMarginLeftRight;
+
+ private final TaskbarActivityContext mActivityContext;
+
+ // Initialized in TaskbarController constructor.
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+
+ LinearLayout mSystemButtonContainer;
+ LinearLayout mHotseatIconsContainer;
+
+ // 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;
+
+ /** Provider of buttons added to taskbar in 3 button nav */
+ private ButtonProvider mButtonProvider;
+
+ private boolean mDisableRelayout;
+ private boolean mAreHolesAllowed;
+
+ public TaskbarView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mActivityContext = ActivityContext.lookupContext(context);
+
+ Resources resources = getResources();
+ mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
+ mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+
+ mIsRtl = Utilities.isRtl(resources);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSystemButtonContainer = findViewById(R.id.system_button_layout);
+ mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
+ }
+
+ protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
+ ButtonProvider buttonProvider) {
+ mIconClickListener = clickListener;
+ mIconLongClickListener = longClickListener;
+ mButtonProvider = buttonProvider;
+
+ if (mActivityContext.canShowNavButtons()) {
+ createNavButtons();
+ } else {
+ mSystemButtonContainer.setVisibility(GONE);
+ }
+
+ int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
+ updateHotseatItems(new ItemInfo[numHotseatIcons]);
+ }
+
+ /**
+ * Enables/disables empty icons in taskbar so that the layout matches with Launcher
+ */
+ public void setHolesAllowedInLayout(boolean areHolesAllowed) {
+ if (mAreHolesAllowed != areHolesAllowed) {
+ mAreHolesAllowed = areHolesAllowed;
+ updateHotseatItemsVisibility();
+ // TODO: Add animation
+ }
+ }
+
+ private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
+ if (mAreHolesAllowed != areHolesAllowed) {
+ mAreHolesAllowed = areHolesAllowed;
+ updateHotseatItemsVisibility();
+ onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
+ makeMeasureSpec(getMeasuredHeight(), EXACTLY));
+ onLayout(false, getLeft(), getTop(), getRight(), getBottom());
+ }
+ }
+
+ /**
+ * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
+ */
+ protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+ for (int i = 0; i < hotseatItemInfos.length; i++) {
+ ItemInfo hotseatItemInfo = hotseatItemInfos[
+ !mIsRtl ? i : hotseatItemInfos.length - i - 1];
+ View hotseatView = mHotseatIconsContainer.getChildAt(i);
+
+ // Replace any Hotseat views with the appropriate type if it's not already that type.
+ final int expectedLayoutResId;
+ boolean isFolder = false;
+ boolean needsReinflate = false;
+ if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+ expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+ } else if (hotseatItemInfo instanceof FolderInfo) {
+ expectedLayoutResId = R.layout.folder_icon;
+ isFolder = true;
+ // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
+ // if the info changes we need to reinflate. This should only happen if a new folder
+ // is dragged to the position that another folder previously existed.
+ needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
+ } else {
+ expectedLayoutResId = R.layout.taskbar_app_icon;
+ }
+ if (hotseatView == null
+ || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+ || needsReinflate) {
+ mHotseatIconsContainer.removeView(hotseatView);
+ if (isFolder) {
+ FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+ FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
+ mActivityContext, this, folderInfo);
+ folderIcon.setTextVisible(false);
+ hotseatView = folderIcon;
+ } else {
+ hotseatView = inflate(expectedLayoutResId);
+ }
+ int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
+ LayoutParams lp = new LayoutParams(iconSize, iconSize);
+ lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+ mHotseatIconsContainer.addView(hotseatView, i, lp);
+ }
+
+ // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
+ if (hotseatView instanceof BubbleTextView
+ && hotseatItemInfo instanceof WorkspaceItemInfo) {
+ ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
+ (WorkspaceItemInfo) hotseatItemInfo);
+ hotseatView.setOnClickListener(mIconClickListener);
+ hotseatView.setOnLongClickListener(mIconLongClickListener);
+ } else if (isFolder) {
+ hotseatView.setOnClickListener(mIconClickListener);
+ hotseatView.setOnLongClickListener(mIconLongClickListener);
+ } else {
+ hotseatView.setOnClickListener(null);
+ hotseatView.setOnLongClickListener(null);
+ hotseatView.setTag(null);
+ }
+ updateHotseatItemVisibility(hotseatView);
+ }
+ }
+
+ protected void updateHotseatItemsVisibility() {
+ for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
+ updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
+ }
+ }
+
+ private void updateHotseatItemVisibility(View hotseatView) {
+ hotseatView.setVisibility(
+ hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
+ }
+
+ @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.
+ * @return Whether a delegate view was chosen and it handled the touch event.
+ */
+ private boolean delegateTouchIfNecessary(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
+ if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
+ View delegateView = findDelegateView(x, y);
+ if (delegateView != null) {
+ mDelegateTargeted = true;
+ mDelegateView = delegateView;
+ mDelegateSlopBounds.set(mTempDelegateBounds);
+ mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
+ }
+ }
+
+ boolean sendToDelegate = mDelegateTargeted;
+ boolean inBounds = true;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ inBounds = mDelegateSlopBounds.contains(x, y);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDelegateTargeted = false;
+ break;
+ }
+
+ boolean handled = false;
+ if (sendToDelegate) {
+ if (inBounds) {
+ // Offset event coordinates to be inside the target view
+ event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
+ } else {
+ // Offset event coordinates to be outside the target view (in case it does
+ // something like tracking pressed state)
+ event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
+ }
+ handled = mDelegateView.dispatchTouchEvent(event);
+ // Cleanup if this was the last event to send to the delegate.
+ if (!mDelegateTargeted) {
+ mDelegateView = null;
+ }
+ }
+ return handled;
+ }
+
+ /**
+ * Return an item whose touch bounds contain the given coordinates,
+ * or null if no such item exists.
+ *
+ * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
+ */
+ private @Nullable View findDelegateView(float x, float y) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (!child.isShown() || !child.isClickable()) {
+ continue;
+ }
+ int childCenterX = child.getLeft() + child.getWidth() / 2;
+ int childCenterY = child.getTop() + child.getHeight() / 2;
+ mTempDelegateBounds.set(
+ childCenterX - mIconTouchSize / 2f,
+ childCenterY - mIconTouchSize / 2f,
+ childCenterX + mIconTouchSize / 2f,
+ childCenterY + mIconTouchSize / 2f);
+ if (mTempDelegateBounds.contains(x, y)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
+ * touch bounds.
+ */
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ getLocationOnScreen(mTempOutLocation);
+ float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
+ float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
+ return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
+ }
+
+ /**
+ * Add back/home/recents buttons into a single ViewGroup that will be inserted at
+ * {@param navButtonStartIndex}
+ */
+ private void createNavButtons() {
+ LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
+ mActivityContext.getDeviceProfile().iconSizePx,
+ mActivityContext.getDeviceProfile().iconSizePx
+ );
+ buttonParams.gravity = Gravity.CENTER;
+
+ mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
+ mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
+ mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
+ }
+
+ @Override
+ public boolean onDragEvent(DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ mIsDraggingItem = true;
+ AbstractFloatingView.closeAllOpenViews(mActivityContext);
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ mIsDraggingItem = false;
+ break;
+ }
+ return super.onDragEvent(event);
+ }
+
+ public boolean isDraggingItem() {
+ return mIsDraggingItem;
+ }
+
+ /**
+ * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
+ */
+ protected RectF getHotseatBounds() {
+ RectF result;
+ mDisableRelayout = true;
+ boolean wereHolesAllowed = mAreHolesAllowed;
+ setHolesAllowedInLayoutNoAnimation(true);
+ result = new RectF(
+ mHotseatIconsContainer.getLeft(),
+ mHotseatIconsContainer.getTop(),
+ mHotseatIconsContainer.getRight(),
+ mHotseatIconsContainer.getBottom());
+ setHolesAllowedInLayoutNoAnimation(wereHolesAllowed);
+ mDisableRelayout = false;
+
+ return result;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mDisableRelayout) {
+ super.requestLayout();
+ }
+ }
+
+ // FolderIconParent implemented methods.
+
+ @Override
+ public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+ mLeaveBehindFolderIcon = child;
+ invalidate();
+ }
+
+ @Override
+ public void clearFolderLeaveBehind(FolderIcon child) {
+ mLeaveBehindFolderIcon = null;
+ invalidate();
+ }
+
+ // End FolderIconParent implemented methods.
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mLeaveBehindFolderIcon != null) {
+ canvas.save();
+ canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop());
+ mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas);
+ canvas.restore();
+ }
+ }
+
+ private View inflate(@LayoutRes int layoutResId) {
+ return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ // Ignore, we just implement Insettable to draw behind system insets.
+ }
+
+ public void setIconsVisibility(boolean isVisible) {
+ mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
+ }
+
+ public boolean areIconsVisible() {
+ return mHotseatIconsContainer.getVisibility() == VISIBLE;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 92ea3fe87b..3b56830b79 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -18,14 +18,24 @@ package com.android.launcher3.uioverrides;
import android.app.Person;
import android.content.pm.ShortcutInfo;
+import android.view.Display;
import com.android.launcher3.Utilities;
public class ApiWrapper {
+ public static final boolean TASKBAR_DRAWN_IN_PROCESS = true;
+
public static Person[] getPersons(ShortcutInfo si) {
if (!Utilities.ATLEAST_Q) return Utilities.EMPTY_PERSON_ARRAY;
Person[] persons = si.getPersons();
return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
}
+
+ /**
+ * Returns true if the display is an internal displays
+ */
+ public static boolean isInternalDisplay(Display display) {
+ return display.getType() == Display.TYPE_INTERNAL;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index aad7e17df0..1d523151db 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -18,19 +18,17 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER;
-import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.util.FloatProperty;
@@ -40,9 +38,9 @@ import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -65,23 +63,18 @@ public abstract class BaseRecentsViewStateController
public void setState(@NonNull LauncherState state) {
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
- ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
+ ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
- OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
- SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
- SCRIM_MULTIPLIER.set(scrim, 1f);
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
+ RECENTS_GRID_PROGRESS.set(mRecentsView,
+ state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
}
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation builder) {
- if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
- // The entire recents animation is played atomically.
- return;
- }
if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
return;
}
@@ -100,23 +93,27 @@ public abstract class BaseRecentsViewStateController
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
- setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
+ setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
+ PagedOrientationHandler orientationHandler =
+ ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
+ FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
+ TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
+ mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, taskViewsFloat,
+ toState.getSplitSelectTranslation(mLauncher), LINEAR);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
- OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
- setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
- config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
- setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f,
- config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
setter.setFloat(
mRecentsView, getTaskModalnessProperty(),
toState.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
+ toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
index 7c0231aeb2..a731ecdd82 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -65,6 +65,20 @@ public class DeviceFlag extends DebugFlag {
mListeners.add(r);
}
+ @Override
+ public void removeChangeListener(Runnable r) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(r);
+ }
+
+ @Override
+ public boolean get() {
+ // Override this method in order to let Robolectric ShadowDeviceFlag to stub it.
+ return super.get();
+ }
+
private void registerDeviceConfigChangedListener(Context context) {
int usagePerm = context.checkCallingOrSelfPermission("android.permission.READ_DEVICE_CONFIG");
if (usagePerm != PackageManager.PERMISSION_GRANTED) return;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
similarity index 73%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
rename to quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 597c17b4e2..d839a36213 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,13 +15,11 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
-import static com.android.launcher3.graphics.IconShape.getShape;
-
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
@@ -29,20 +27,17 @@ import android.os.Process;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
+import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
@@ -53,8 +48,7 @@ import com.android.launcher3.views.DoubleShadowBubbleTextView;
/**
* A BubbleTextView with a ring around it's drawable
*/
-public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
- LauncherAccessibilityDelegate.AccessibilityActionHandler {
+public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
@@ -63,9 +57,13 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path mRingPath = new Path();
- private boolean mIsPinned = false;
- private final int mNormalizedIconRadius;
+ private final int mNormalizedIconSize;
+ private final Path mShapePath;
+ private final Matrix mTmpMatrix = new Matrix();
+
private final BlurMaskFilter mShadowFilter;
+
+ private boolean mIsPinned = false;
private int mPlateColor;
boolean mDrawForDrag = false;
@@ -80,19 +78,18 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
- mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
+ mNormalizedIconSize = IconNormalizer.getNormalizedCircleSize(getIconSize());
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_thin_outline);
mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
+ mShapePath = GraphicsUtils.getShapePath(mNormalizedIconSize);
}
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
if (!mIsPinned) {
- boolean isBadged = getTag() instanceof WorkspaceItemInfo
- && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
- drawEffect(canvas, isBadged);
+ drawEffect(canvas);
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
@@ -114,8 +111,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
@Override
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
super.applyFromWorkspaceItem(info);
- int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
- mPlateColor = ColorUtils.setAlphaComponent(color, 200);
+ mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -147,29 +143,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
verifyHighRes();
}
- @Override
- public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
- if (!mIsPinned) {
- accessibilityNodeInfo.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
- getContext().getText(R.string.pin_prediction)));
- }
- }
-
- @Override
- public boolean performAccessibilityAction(int action, ItemInfo info) {
- QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
- if (action == PIN_PREDICTION) {
- if (launcher == null || launcher.getHotseatPredictionController() == null) {
- return false;
- }
- HotseatPredictionController controller = launcher.getHotseatPredictionController();
- controller.pinPrediction(info);
- return true;
- }
- return false;
- }
-
@Override
public void getIconBounds(Rect outBounds) {
super.getIconBounds(outBounds);
@@ -179,31 +152,63 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
}
}
+ public boolean isPinned() {
+ return mIsPinned;
+ }
+
private int getOutlineOffsetX() {
- return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
+ return (getMeasuredWidth() - mNormalizedIconSize) / 2;
}
private int getOutlineOffsetY() {
- return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ if (mDisplay != DISPLAY_TASKBAR) {
+ return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ }
+ return (getMeasuredHeight() - mNormalizedIconSize) / 2;
}
- private void drawEffect(Canvas canvas, boolean isBadged) {
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ updateRingPath();
+ }
+
+ @Override
+ public void setTag(Object tag) {
+ super.setTag(tag);
+ updateRingPath();
+ }
+
+ private void updateRingPath() {
+ boolean isBadged = false;
+ if (getTag() instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
+ isBadged = !Process.myUserHandle().equals(info.user)
+ || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+ || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ }
+
+ mRingPath.reset();
+ mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
+
+ mRingPath.addPath(mShapePath, mTmpMatrix);
+ if (isBadged) {
+ float outlineSize = mNormalizedIconSize * RING_EFFECT_RATIO;
+ float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
+ float scale = badgeSize / mNormalizedIconSize;
+ mTmpMatrix.postTranslate(mNormalizedIconSize, mNormalizedIconSize);
+ mTmpMatrix.preScale(scale, scale);
+ mTmpMatrix.preTranslate(-mNormalizedIconSize, -mNormalizedIconSize);
+ mRingPath.addPath(mShapePath, mTmpMatrix);
+ }
+ }
+
+ private void drawEffect(Canvas canvas) {
// Don't draw ring effect if item is about to be dragged.
if (mDrawForDrag) {
return;
}
- mRingPath.reset();
- getShape().addToPath(mRingPath, getOutlineOffsetX(), getOutlineOffsetY(),
- mNormalizedIconRadius);
- if (isBadged) {
- float outlineSize = mNormalizedIconRadius * RING_EFFECT_RATIO * 2;
- float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
- float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
- float badgeInset = mNormalizedIconRadius * 2 - badgeSize;
- getShape().addToPath(mRingPath, getOutlineOffsetX() + badgeInset,
- getOutlineOffsetY() + badgeInset, badgeSize / 2);
-
- }
mIconRingPaint.setColor(RING_SHADOW_COLOR);
mIconRingPaint.setMaskFilter(mShadowFilter);
canvas.drawPath(mRingPath, mIconRingPaint);
@@ -249,17 +254,13 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
*/
public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
- private int mOffsetX;
- private int mOffsetY;
- private int mIconRadius;
- private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final PredictedAppIcon mIcon;
+ private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
mDelegateCellX = cellX;
mDelegateCellY = cellY;
- mOffsetX = icon.getOutlineOffsetX();
- mOffsetY = icon.getOutlineOffsetY();
- mIconRadius = icon.mNormalizedIconRadius;
+ mIcon = icon;
mOutlinePaint.setStyle(Paint.Style.FILL);
mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
}
@@ -269,7 +270,10 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
*/
@Override
public void drawUnderItem(Canvas canvas) {
- getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
+ canvas.save();
+ canvas.translate(mIcon.getOutlineOffsetX(), mIcon.getOutlineOffsetY());
+ canvas.drawPath(mIcon.mShapePath, mOutlinePaint);
+ canvas.restore();
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
rename to quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
new file mode 100644
index 0000000000..c2c721adf8
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.window.SplashScreen;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** Provides a Quickstep specific animation when launching an activity from an app widget. */
+class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
+
+ private static final String TAG = "QuickstepInteractionHandler";
+
+ private final QuickstepLauncher mLauncher;
+
+ QuickstepInteractionHandler(QuickstepLauncher launcher) {
+ mLauncher = launcher;
+ }
+
+ @SuppressWarnings("NewApi")
+ @Override
+ public boolean onInteraction(View view, PendingIntent pendingIntent,
+ RemoteViews.RemoteResponse remoteResponse) {
+ LauncherAppWidgetHostView hostView = findHostViewAncestor(view);
+ if (hostView == null) {
+ Log.e(TAG, "View did not have a LauncherAppWidgetHostView ancestor.");
+ return RemoteViews.startPendingIntent(hostView, pendingIntent,
+ remoteResponse.getLaunchOptions(view));
+ }
+ Pair options = remoteResponse.getLaunchOptions(view);
+ ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
+ .getActivityLaunchOptions(hostView);
+ if (Utilities.ATLEAST_S && !pendingIntent.isActivity()) {
+ // In the event this pending intent eventually launches an activity, i.e. a trampoline,
+ // use the Quickstep transition animation.
+ try {
+ ActivityTaskManager.getService()
+ .registerRemoteAnimationForNextActivityStart(
+ pendingIntent.getCreatorPackage(),
+ activityOptions.options.getRemoteAnimationAdapter());
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+ activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ Object itemInfo = hostView.getTag();
+ if (itemInfo instanceof ItemInfo) {
+ mLauncher.addLaunchCookie((ItemInfo) itemInfo, activityOptions.options);
+ }
+ options = Pair.create(options.first, activityOptions.options);
+ if (pendingIntent.isActivity()) {
+ logAppLaunch(itemInfo);
+ }
+ return RemoteViews.startPendingIntent(hostView, pendingIntent, options);
+ }
+
+ /**
+ * Logs that the app was launched from the widget.
+ * @param itemInfo the widget info.
+ */
+ private void logAppLaunch(Object itemInfo) {
+ StatsLogManager.StatsLogger logger = mLauncher.getStatsLogManager().logger();
+ if (itemInfo instanceof ItemInfo) {
+ logger.withItemInfo((ItemInfo) itemInfo);
+ }
+ logger.log(LAUNCHER_APP_LAUNCH_TAP);
+ }
+
+ private LauncherAppWidgetHostView findHostViewAncestor(View v) {
+ while (v != null) {
+ if (v instanceof LauncherAppWidgetHostView) return (LauncherAppWidgetHostView) v;
+ v = (View) v.getParent();
+ }
+ return null;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
similarity index 56%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
rename to quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 3133bbb4f7..ec9893c84f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -17,64 +17,67 @@ package com.android.launcher3.uioverrides;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_WIDGET_APP_START;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
-import android.os.Bundle;
-import android.util.Log;
+import android.view.HapticFeedbackConstants;
import android.view.View;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.QuickstepAccessibilityDelegate;
import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.Folder;
+import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
-import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.util.IntArray;
+import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -82,7 +85,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.OptionalInt;
+import java.util.Objects;
import java.util.stream.Stream;
public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -94,33 +97,63 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.createPredictor();
- }
- }
+ private FixedContainerItems mAllAppsPredictions;
+ private HotseatPredictionController mHotseatPredictionController;
@Override
protected void setupViews() {
super.setupViews();
- if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
- mHotseatPredictionController = new HotseatPredictionController(this);
- }
+ mHotseatPredictionController = new HotseatPredictionController(this);
}
@Override
protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+ // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
+ // LiveSearchManager to recreate the AllApps session on the server side.
+ if (mAllAppsSessionLogId != null && ALL_APPS.equals(
+ getStateManager().getCurrentStableState())) {
+ instanceId = mAllAppsSessionLogId;
+ }
+
StatsLogger logger = getStatsLogManager()
.logger().withItemInfo(info).withInstanceId(instanceId);
- OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
- allAppsRank.ifPresent(logger::withRank);
+
+ if (mAllAppsPredictions != null
+ && (info.itemType == ITEM_TYPE_APPLICATION
+ || info.itemType == ITEM_TYPE_SHORTCUT
+ || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
+ int count = mAllAppsPredictions.items.size();
+ for (int i = 0; i < count; i++) {
+ ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
+ if (targetInfo.itemType == info.itemType
+ && targetInfo.user.equals(info.user)
+ && Objects.equals(targetInfo.getIntent(), info.getIntent())) {
+ logger.withRank(i);
+ break;
+ }
+
+ }
+ }
logger.log(LAUNCHER_APP_LAUNCH_TAP);
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
- }
+ mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
+ }
+
+ @Override
+ protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+ return new QuickstepAccessibilityDelegate(this);
+ }
+
+ /**
+ * Returns Prediction controller for hybrid hotseat
+ */
+ public HotseatPredictionController getHotseatPredictionController() {
+ return mHotseatPredictionController;
+ }
+
+ @Override
+ protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+ return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
@Override
@@ -130,12 +163,10 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
@Override
- public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
- @Nullable String sourceContainer) {
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.setPauseUIUpdate(true);
- }
- return super.startActivitySafely(v, intent, item, sourceContainer);
+ public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+ // Only pause is taskbar controller is not present
+ mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
+ return super.startActivitySafely(v, intent, item);
}
@Override
@@ -146,36 +177,22 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
}
- if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
+ if (((changeBits & ACTIVITY_STATE_STARTED) != 0
|| (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
mHotseatPredictionController.setPauseUIUpdate(false);
}
}
@Override
- public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
- super.folderCreatedFromItem(folder, itemInfo);
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
- }
- }
-
- @Override
- public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
- super.folderConvertedToItem(folder, itemInfo);
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
- }
+ protected void showAllAppsFromIntent(boolean alreadyOnHome) {
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+ super.showAllAppsFromIntent(alreadyOnHome);
}
@Override
public Stream