blob: 6a936ab73da226ae7e592f9918662b1e5d5944b8 [file] [log] [blame]
/*
* 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.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AnimatedFloat.VALUE;
import android.graphics.Rect;
import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.AnimatedFloat;
/**
* Handles properties/data collection, then passes the results to TaskbarView to render.
*/
public class TaskbarViewController {
private static final Runnable NO_OP = () -> { };
public static final int ALPHA_INDEX_HOME = 0;
public static final int ALPHA_INDEX_IME = 1;
public static final int ALPHA_INDEX_KEYGUARD = 2;
public static final int ALPHA_INDEX_STASH = 3;
public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 5;
private static final int NUM_ALPHA_CHANNELS = 6;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
private final MultiValueAlpha mTaskbarIconAlpha;
private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
this::updateTranslationY);
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
this::updateTranslationY);
private AnimatedFloat mTaskbarNavButtonTranslationY;
private final TaskbarModelCallbacks mModelCallbacks;
// Initialized in init.
private TaskbarControllers mControllers;
// Animation to align icons with Launcher, created lazily. This allows the controller to be
// active only during the animation and does not need to worry about layout changes.
private AnimatorPlaybackController mIconAlignControllerLazy = null;
private Runnable mOnControllerPreCreateCallback = NO_OP;
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
}
public void init(TaskbarControllers controllers) {
mControllers = controllers;
mTaskbarView.init(new TaskbarViewCallbacks());
mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
mTaskbarIconScaleForStash.updateValue(1f);
mModelCallbacks.init(controllers);
if (mActivity.isUserSetupComplete()) {
// Only load the callbacks if user setup is completed
LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
}
mTaskbarNavButtonTranslationY =
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
}
public void onDestroy() {
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
}
public boolean areIconsVisible() {
return mTaskbarView.areIconsVisible();
}
public MultiValueAlpha getTaskbarIconAlpha() {
return mTaskbarIconAlpha;
}
/**
* Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
*/
public void setImeIsVisible(boolean isImeVisible) {
mTaskbarView.setTouchesEnabled(!isImeVisible);
}
/**
* Should be called when the recents button is disabled, so we can hide taskbar icons as well.
*/
public void setRecentsButtonDisabled(boolean isDisabled) {
// TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
mTaskbarIconAlpha.getProperty(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
}
/**
* Sets OnClickListener and OnLongClickListener for the given view.
*/
public void setClickAndLongClickListenersForIcon(View icon) {
mTaskbarView.setClickAndLongClickListenersForIcon(icon);
}
/**
* Adds one time pre draw listener to the taskbar view, it is called before
* drawing a frame and invoked only once
* @param listener callback that will be invoked before drawing the next frame
*/
public void addOneTimePreDrawListener(Runnable listener) {
mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
listener.run();
viewTreeObserver.removeOnPreDrawListener(this);
}
return true;
}
});
}
public Rect getIconLayoutBounds() {
return mTaskbarView.getIconLayoutBounds();
}
public View[] getIconViews() {
return mTaskbarView.getIconViews();
}
public AnimatedFloat getTaskbarIconScaleForStash() {
return mTaskbarIconScaleForStash;
}
public AnimatedFloat getTaskbarIconTranslationYForStash() {
return mTaskbarIconTranslationYForStash;
}
/**
* Applies scale properties for the entire TaskbarView (rather than individual icons).
*/
private void updateScale() {
float scale = mTaskbarIconScaleForStash.value;
mTaskbarView.setScaleX(scale);
mTaskbarView.setScaleY(scale);
}
private void updateTranslationY() {
mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
+ mTaskbarIconTranslationYForStash.value);
}
/**
* Sets the taskbar icon alignment relative to Launcher hotseat icons
* @param alignmentRatio [0, 1]
* 0 => not aligned
* 1 => fully aligned
*/
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
if (mIconAlignControllerLazy == null) {
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
}
mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
if (alignmentRatio <= 0 || alignmentRatio >= 1) {
// Cleanup lazy controller so that it is created again in next animation
mIconAlignControllerLazy = null;
}
}
/**
* Creates an animation for aligning the taskbar icons with the provided Launcher device profile
*/
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
mOnControllerPreCreateCallback.run();
PendingAnimation setter = new PendingAnimation(100);
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
int hotseatCellSize =
(launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
/ launcherDp.numShownHotseatIcons;
int offsetY = launcherDp.getTaskbarOffsetY();
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, LINEAR);
int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
int expandedHeight = Math.max(collapsedHeight,
mActivity.getDeviceProfile().taskbarSize + offsetY);
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
int count = mTaskbarView.getChildCount();
for (int i = 0; i < count; i++) {
View child = mTaskbarView.getChildAt(i);
ItemInfo info = (ItemInfo) child.getTag();
setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
float childCenter = (child.getLeft() + child.getRight()) / 2;
float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ hotseatCellSize / 2;
setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
}
AnimatorPlaybackController controller = setter.createPlaybackController();
mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0);
return controller;
}
public void onRotationChanged(DeviceProfile deviceProfile) {
if (areIconsVisible()) {
// We only translate on rotation when on home
return;
}
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
}
/**
* Callbacks for {@link TaskbarView} to interact with its controller.
*/
public class TaskbarViewCallbacks {
private final float mSquaredTouchSlop = Utilities.squaredTouchSlop(mActivity);
private float mDownX, mDownY;
private boolean mCanceledStashHint;
public View.OnClickListener getIconOnClickListener() {
return mActivity::onTaskbarIconClicked;
}
public View.OnLongClickListener getIconOnLongClickListener() {
return mControllers.taskbarDragController::startDragOnLongClick;
}
public View.OnLongClickListener getBackgroundOnLongClickListener() {
return view -> mControllers.taskbarStashController
.updateAndAnimateIsManuallyStashedInApp(true);
}
/**
* Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
* consume the touch so TaskbarView treats it as an ACTION_CANCEL.
*/
public boolean onTouchEvent(MotionEvent motionEvent) {
final float x = motionEvent.getRawX();
final float y = motionEvent.getRawY();
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
mDownY = y;
mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
mCanceledStashHint = false;
break;
case MotionEvent.ACTION_MOVE:
if (!mCanceledStashHint
&& squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
mControllers.taskbarStashController.startStashHint(
/* animateForward= */ false);
mCanceledStashHint = true;
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (!mCanceledStashHint) {
mControllers.taskbarStashController.startStashHint(
/* animateForward= */ false);
}
break;
}
return false;
}
}
public static final FloatProperty<View> ICON_TRANSLATE_X =
new FloatProperty<View>("taskbarAligmentTranslateX") {
@Override
public void setValue(View view, float v) {
if (view instanceof BubbleTextView) {
((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
} else if (view instanceof FolderIcon) {
((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
} else {
view.setTranslationX(v);
}
}
@Override
public Float get(View view) {
if (view instanceof BubbleTextView) {
return ((BubbleTextView) view)
.getTranslationXForTaskbarAlignmentAnimation();
} else if (view instanceof FolderIcon) {
return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
}
return view.getTranslationX();
}
};
}