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 e302b4f152e3d01ccd411d6441c8b6ae46998e2f..4d7cc85ed1809a8b5fe6bdd87f95851adfe8802f 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 13501a452b959d9c1be18c67971207b07f07e592..1f268cc9d940c06560ffde67bcf1d27eb6b83577 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 fe8f0c653d1228f800540fb6a88047f6e840c2f8..370fb8ef7c55ac6ab058d68f1213f6010bc03388 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,11 +41,10 @@ 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 java.util.function.Consumer;
+
/**
* Controls blur and wallpaper zoom, for the Launcher surface only.
*/
@@ -91,23 +94,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;
@@ -123,6 +140,7 @@ public class DepthController implements StateHandler,
mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
mWallpaperManager = new WallpaperManagerCompat(mLauncher);
}
+
if (mLauncher.getRootView() != null && mOnAttachListener == null) {
mOnAttachListener = new View.OnAttachStateChangeListener() {
@Override
@@ -132,13 +150,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);
+ }
}
}
@@ -157,22 +182,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);
}
}
}
@@ -186,15 +206,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;
}
@@ -205,6 +225,19 @@ public class DepthController implements StateHandler,
}
}
+ /**
+ * 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) {
depth = Utilities.boundToRange(depth, 0, 1);
// Round out the depth to dedupe frequent, non-perceptable updates
@@ -213,32 +246,36 @@ public class DepthController implements StateHandler,
if (Float.compare(mDepth, depthF) == 0) {
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;
- if (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 0000000000000000000000000000000000000000..540f7483130324d517a5b9275dd3a3bd04ba3e03
--- /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 0000000000000000000000000000000000000000..287caab44b11c96dfc0189904d5e884668b445ac
--- /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 0000000000000000000000000000000000000000..c2d107c22dc56ff37d86a742be165a4695616cab
--- /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 0000000000000000000000000000000000000000..4ba0ee0d41d0f1abf37430803d6c2ccbb847db5e
--- /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