blob: a219ac6324115ccbc34d83eb3c55fb5839fc7fbb [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 android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.annotation.ColorInt;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.TaskTransitionSpec;
import android.view.WindowManagerGlobal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsAnimationCallbacks;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
/**
* A data source which integrates with a Launcher instance
*/
public class LauncherTaskbarUIController extends TaskbarUIController {
private static final String TAG = "TaskbarUIController";
public static final int MINUS_ONE_PAGE_PROGRESS_INDEX = 0;
public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
private final SparseArray<Float> mTaskbarInAppDisplayProgress = new SparseArray<>(4);
private final QuickstepLauncher mLauncher;
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
dp -> {
onStashedInAppChanged(dp);
if (mControllers != null && mControllers.taskbarViewController != null) {
mControllers.taskbarViewController.onRotationChanged(dp);
}
};
// Initialized in init.
private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
private TaskbarKeyguardController mKeyguardController;
private final TaskbarLauncherStateController
mTaskbarLauncherStateController = new TaskbarLauncherStateController();
public LauncherTaskbarUIController(QuickstepLauncher launcher) {
mLauncher = launcher;
}
@Override
protected void init(TaskbarControllers taskbarControllers) {
super.init(taskbarControllers);
mTaskbarLauncherStateController.init(mControllers, mLauncher);
mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
.getOverrideBackgroundAlpha();
mLauncher.setTaskbarUIController(this);
mKeyguardController = taskbarControllers.taskbarKeyguardController;
onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
onStashedInAppChanged(mLauncher.getDeviceProfile());
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
onLauncherResumedOrPaused(false);
mTaskbarLauncherStateController.onDestroy();
mLauncher.setTaskbarUIController(null);
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
updateTaskTransitionSpec(true);
}
@Override
protected boolean isTaskbarTouchable() {
return !mTaskbarLauncherStateController.isAnimatingToLauncher();
}
public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
mTaskbarLauncherStateController.setShouldDelayLauncherStateAnim(
shouldDelayLauncherStateAnim);
}
/**
* Adds the Launcher resume animator to the given animator set.
*
* This should be used to run a Launcher resume animation whose progress matches a
* swipe progress.
*
* @param placeholderDuration a placeholder duration to be used to ensure all full-length
* sub-animations are properly coordinated. This duration should not
* actually be used since this animation tracks a swipe progress.
*/
protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) {
animation.play(onLauncherResumedOrPaused(
/* isResumed= */ true,
/* fromInit= */ false,
/* startAnimation= */ false,
placeholderDuration));
}
/**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
onLauncherResumedOrPaused(isResumed, false /* fromInit */);
}
private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
onLauncherResumedOrPaused(
isResumed,
fromInit,
/* startAnimation= */ true,
!isResumed
? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
: QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
}
@Nullable
private Animator onLauncherResumedOrPaused(
boolean isResumed, boolean fromInit, boolean startAnimation, int duration) {
if (mKeyguardController.isScreenOff()) {
if (!isResumed) {
return null;
} else {
// Resuming implicitly means device unlocked
mKeyguardController.setScreenOn();
}
}
if (ENABLE_SHELL_TRANSITIONS
&& !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
// Launcher is resumed, but in a state where taskbar is still independent, so
// ignore the state change.
return null;
}
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
}
/**
* Create Taskbar animation when going from an app to Launcher as part of recents transition.
* @param toState If known, the state we will end up in when reaching Launcher.
* @param callbacks callbacks to track the recents animation lifecycle. The state change is
* automatically reset once the recents animation finishes
*/
public Animator createAnimToLauncher(@NonNull LauncherState toState,
@NonNull RecentsAnimationCallbacks callbacks, long duration) {
return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
}
/**
* @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 mControllers.taskbarViewController.isEventOverAnyItem(ev)
|| mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
}
public boolean isDraggingItem() {
return mControllers.taskbarDragController.isDragging();
}
@Override
protected void onStashedInAppChanged() {
onStashedInAppChanged(mLauncher.getDeviceProfile());
}
private void onStashedInAppChanged(DeviceProfile deviceProfile) {
boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
updateTaskTransitionSpec(taskbarStashedInApps);
}
private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
try {
if (taskbarIsHidden) {
// Clear custom task transition settings when the taskbar is stashed
WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
} else {
// Adjust task transition spec to account for taskbar being visible
@ColorInt int taskAnimationBackgroundColor =
mLauncher.getColor(R.color.taskbar_background);
TaskTransitionSpec customTaskAnimationSpec = new TaskTransitionSpec(
taskAnimationBackgroundColor,
Set.of(ITYPE_EXTRA_NAVIGATION_BAR)
);
WindowManagerGlobal.getWindowManagerService()
.setTaskTransitionSpec(customTaskAnimationSpec);
}
} catch (RemoteException e) {
// This shouldn't happen but if it does task animations won't look good until the
// taskbar stashing state is changed.
Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
e);
}
}
/**
* Sets whether the background behind the taskbar/nav bar should be hidden.
*/
public void forceHideBackground(boolean forceHide) {
mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
}
@Override
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
}
/**
* Starts the taskbar education flow, if the user hasn't seen it yet.
*/
public void showEdu() {
if (!shouldShowEdu()) {
return;
}
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
mControllers.taskbarEduController.showEdu();
}
/**
* Whether the taskbar education should be shown.
*/
public boolean shouldShowEdu() {
return !Utilities.IS_RUNNING_IN_TEST_HARNESS
&& !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
}
/**
* Manually ends the taskbar education flow.
*/
public void hideEdu() {
mControllers.taskbarEduController.hideEdu();
}
@Override
public void onTaskbarIconLaunched(ItemInfo item) {
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
instanceId);
}
@Override
public void setSystemGestureInProgress(boolean inProgress) {
super.setSystemGestureInProgress(inProgress);
// Launcher's ScrimView will draw the background throughout the gesture. But once the
// gesture ends, start drawing taskbar's background again since launcher might stop drawing.
forceHideBackground(inProgress);
}
/**
* Animates Taskbar elements during a transition to a Launcher state that should use in-app
* layouts.
*
* @param progress [0, 1]
* 0 => use home layout
* 1 => use in-app layout
*/
public void onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex) {
if (mControllers == null) {
// This method can be called before init() is called.
return;
}
mTaskbarInAppDisplayProgress.put(progressIndex, progress);
if (!mControllers.taskbarStashController.isInApp()
&& !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
// Only animate the nav buttons while home and not animating home, otherwise let
// the TaskbarViewController handle it.
mControllers.navbarButtonsViewController
.getTaskbarNavButtonTranslationYForInAppDisplay()
.updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
* getInAppDisplayProgress());
}
}
/** Returns true iff any in-app display progress > 0. */
public boolean shouldUseInAppLayout() {
return getInAppDisplayProgress() > 0;
}
private float getInAppDisplayProgress(int index) {
if (!mTaskbarInAppDisplayProgress.contains(index)) {
mTaskbarInAppDisplayProgress.put(index, 0f);
}
return mTaskbarInAppDisplayProgress.get(index);
}
private float getInAppDisplayProgress() {
return Stream.of(
getInAppDisplayProgress(MINUS_ONE_PAGE_PROGRESS_INDEX),
getInAppDisplayProgress(ALL_APPS_PAGE_PROGRESS_INDEX),
getInAppDisplayProgress(WIDGETS_PAGE_PROGRESS_INDEX),
getInAppDisplayProgress(SYSUI_SURFACE_PROGRESS_INDEX))
.max(Float::compareTo)
.get();
}
@Override
public void onExpandPip() {
super.onExpandPip();
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false);
mTaskbarLauncherStateController.applyState();
}
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
super.dumpLogs(prefix, pw);
pw.println(String.format(
"%s\tmTaskbarOverrideBackgroundAlpha=%.2f",
prefix,
mTaskbarOverrideBackgroundAlpha.value));
pw.println(String.format("%s\tTaskbar in-app display progress:", prefix));
if (mControllers == null) {
pw.println(String.format("%s\t\tMissing mControllers", prefix));
} else {
pw.println(String.format(
"%s\t\tprogress at MINUS_ONE_PAGE_PROGRESS_INDEX=%.2f",
prefix,
getInAppDisplayProgress(MINUS_ONE_PAGE_PROGRESS_INDEX)));
pw.println(String.format(
"%s\t\tprogress at ALL_APPS_PAGE_PROGRESS_INDEX=%.2f",
prefix,
getInAppDisplayProgress(ALL_APPS_PAGE_PROGRESS_INDEX)));
pw.println(String.format(
"%s\t\tprogress at WIDGETS_PAGE_PROGRESS_INDEX=%.2f",
prefix,
getInAppDisplayProgress(WIDGETS_PAGE_PROGRESS_INDEX)));
pw.println(String.format(
"%s\t\tprogress at SYSUI_SURFACE_PROGRESS_INDEX=%.2f",
prefix,
getInAppDisplayProgress(SYSUI_SURFACE_PROGRESS_INDEX)));
}
mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
}
}