blob: 0f0d572f8eaedf6ad3aa1c537775ecd1fa3f8a99 [file] [log] [blame]
/*
* Copyright (C) 2023 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.wm.shell.desktopmode;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
* Animated visual indicator for Desktop Mode windowing transitions.
*/
public class DesktopModeVisualIndicator {
private final Context mContext;
private final DisplayController mDisplayController;
private final ShellTaskOrganizer mTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
private final SurfaceControl mTaskSurface;
private SurfaceControl mLeash;
private final SyncTransactionQueue mSyncQueue;
private SurfaceControlViewHost mViewHost;
private View mView;
private boolean mIsFullscreen;
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
mSyncQueue = syncQueue;
mTaskInfo = taskInfo;
mDisplayController = displayController;
mContext = context;
mTaskSurface = taskSurface;
mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
createView();
}
/**
* Create a fullscreen indicator with no animation
*/
private void createView() {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mContext.getResources();
final DisplayMetrics metrics = resources.getDisplayMetrics();
final int screenWidth = metrics.widthPixels;
final int screenHeight = metrics.heightPixels;
mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
mLeash = builder
.setName("Fullscreen Indicator")
.setContainerLayer()
.build();
t.show(mLeash);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(screenWidth, screenHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
null /* hostInputToken */);
mViewHost = new SurfaceControlViewHost(mContext,
mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
"FullscreenVisualIndicator");
mViewHost.setView(mView, lp);
// We want this indicator to be behind the dragged task, but in front of all others.
t.setRelativeLayer(mLeash, mTaskSurface, -1);
mSyncQueue.runInSync(transaction -> {
transaction.merge(t);
t.close();
});
}
/**
* Create fullscreen indicator and fades it in.
*/
public void createFullscreenIndicator() {
mIsFullscreen = true;
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
/**
* Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
*/
public void createFullscreenIndicatorWithAnimatedBounds() {
mIsFullscreen = true;
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
.toFullscreenAnimatorWithAnimatedBounds(mView,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
/**
* Takes existing fullscreen indicator and animates it to freeform bounds
*/
public void transitionFullscreenIndicatorToFreeform() {
mIsFullscreen = false;
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
/**
* Takes the existing freeform indicator and animates it to fullscreen
*/
public void transitionFreeformIndicatorToFullscreen() {
mIsFullscreen = true;
final VisualIndicatorAnimator animator =
VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
/**
* Release the indicator and its components when it is no longer needed.
*/
public void releaseVisualIndicator(SurfaceControl.Transaction t) {
if (mViewHost == null) return;
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
}
if (mLeash != null) {
t.remove(mLeash);
mLeash = null;
}
}
/**
* Returns true if visual indicator is fullscreen
*/
public boolean isFullscreen() {
return mIsFullscreen;
}
/**
* Animator for Desktop Mode transitions which supports bounds and alpha animation.
*/
private static class VisualIndicatorAnimator extends ValueAnimator {
private static final int FULLSCREEN_INDICATOR_DURATION = 200;
private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
private static final float INDICATOR_FINAL_OPACITY = 0.7f;
private final View mView;
private final Rect mStartBounds;
private final Rect mEndBounds;
private final RectEvaluator mRectEvaluator;
private VisualIndicatorAnimator(View view, Rect startBounds,
Rect endBounds) {
mView = view;
mStartBounds = new Rect(startBounds);
mEndBounds = endBounds;
setFloatValues(0, 1);
mRectEvaluator = new RectEvaluator(new Rect());
}
/**
* Create animator for visual indicator of fullscreen transition
*
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
*/
public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
@NonNull DisplayLayout displayLayout) {
final Rect bounds = getMaxBounds(displayLayout);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, bounds, bounds);
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
}
/**
* Create animator for visual indicator of fullscreen transition
*
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
*/
public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
@NonNull View view, @NonNull DisplayLayout displayLayout) {
final int padding = displayLayout.stableInsets().top;
Rect startBounds = new Rect(padding, padding,
displayLayout.width() - padding, displayLayout.height() - padding);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, getMaxBounds(displayLayout));
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
}
/**
* Create animator for visual indicator of freeform transition
*
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
*/
public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
@NonNull DisplayLayout displayLayout) {
final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
final int width = displayLayout.width();
final int height = displayLayout.height();
Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
(int) (adjustmentPercentage * height / 2),
(int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
(int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, getMaxBounds(displayLayout), endBounds);
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
}
/**
* Add necessary listener for animation of indicator
*/
private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
animator.addUpdateListener(a -> {
if (animator.mView != null) {
animator.updateBounds(a.getAnimatedFraction(), animator.mView);
animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
} else {
animator.cancel();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animator.mView.getBackground().setBounds(animator.mEndBounds);
}
});
animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
}
/**
* Update bounds of view based on current animation fraction.
* Use of delta is to animate bounds independently, in case we need to
* run multiple animations simultaneously.
*
* @param fraction fraction to use, compared against previous fraction
* @param view the view to update
*/
private void updateBounds(float fraction, View view) {
if (mStartBounds.equals(mEndBounds)) {
return;
}
Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
view.getBackground().setBounds(currentBounds);
}
/**
* Fade in the fullscreen indicator
*
* @param fraction current animation fraction
*/
private void updateIndicatorAlpha(float fraction, View view) {
view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
}
/**
* Return the max bounds of a fullscreen indicator
*/
private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
final int padding = displayLayout.stableInsets().top;
final int width = displayLayout.width() - 2 * padding;
final int height = displayLayout.height() - 2 * padding;
Rect endBounds = new Rect((int) (padding
- (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
(int) (padding
- (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
(int) (displayLayout.width() - padding
+ (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
(int) (displayLayout.height() - padding
+ (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
return endBounds;
}
}
}