1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep.views; 17 18 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; 19 import static com.android.app.animation.Interpolators.LINEAR; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.AnimatorSet; 24 import android.animation.ObjectAnimator; 25 import android.animation.ValueAnimator; 26 import android.content.Context; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.widget.LinearLayout; 30 31 import androidx.annotation.Nullable; 32 33 import com.android.launcher3.Launcher; 34 import com.android.launcher3.R; 35 import com.android.launcher3.Utilities; 36 37 /** 38 * Floating view show on launcher home screen that notifies the user that an app will be launched to 39 * the desktop. 40 */ 41 public class DesktopAppSelectView extends LinearLayout { 42 43 private static final int SHOW_INITIAL_HEIGHT_DP = 7; 44 private static final int SHOW_CONTAINER_SCALE_DURATION = 333; 45 private static final int SHOW_CONTAINER_ALPHA_DURATION = 83; 46 private static final int SHOW_CONTENT_ALPHA_DELAY = 67; 47 private static final int SHOW_CONTENT_ALPHA_DURATION = 83; 48 private static final int HIDE_DURATION = 83; 49 50 private final Launcher mLauncher; 51 52 private View mText; 53 private View mCloseButton; 54 @Nullable 55 private Runnable mOnCloseCallback; 56 private AnimatorSet mShowAnimation; 57 private Animator mHideAnimation; 58 DesktopAppSelectView(Context context)59 public DesktopAppSelectView(Context context) { 60 this(context, null); 61 } 62 DesktopAppSelectView(Context context, AttributeSet attrs)63 public DesktopAppSelectView(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr)67 public DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr) { 68 this(context, attrs, defStyleAttr, 0); 69 } 70 DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)71 public DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr, 72 int defStyleRes) { 73 super(context, attrs, defStyleAttr, defStyleRes); 74 mLauncher = Launcher.getLauncher(context); 75 } 76 77 /** 78 * Show the popup on launcher home screen 79 * 80 * @param onCloseCallback optional callback that is called when user clicks the close button 81 * @return the created view 82 */ show(Launcher launcher, @Nullable Runnable onCloseCallback)83 public static DesktopAppSelectView show(Launcher launcher, @Nullable Runnable onCloseCallback) { 84 DesktopAppSelectView view = (DesktopAppSelectView) launcher.getLayoutInflater().inflate( 85 R.layout.floating_desktop_app_select, launcher.getDragLayer(), false); 86 view.setOnCloseClickCallback(onCloseCallback); 87 view.show(); 88 return view; 89 } 90 91 @Override onFinishInflate()92 protected void onFinishInflate() { 93 super.onFinishInflate(); 94 mText = findViewById(R.id.desktop_app_select_text); 95 mCloseButton = findViewById(R.id.close_button); 96 mCloseButton.setOnClickListener(v -> { 97 if (mHideAnimation == null) { 98 hide(); 99 if (mOnCloseCallback != null) { 100 mOnCloseCallback.run(); 101 } 102 } 103 }); 104 } 105 show()106 private void show() { 107 mLauncher.getDragLayer().addView(this); 108 109 // Set up initial values 110 getBackground().setAlpha(0); 111 mText.setAlpha(0); 112 mCloseButton.setAlpha(0); 113 int initialHeightPx = Utilities.dpToPx(SHOW_INITIAL_HEIGHT_DP); 114 int finalHeight = getResources().getDimensionPixelSize( 115 R.dimen.desktop_mode_floating_app_select_height); 116 float initialScale = initialHeightPx / (float) finalHeight; 117 setScaleY(initialScale); 118 setPivotY(0); 119 120 // Animate the container 121 ValueAnimator containerBackground = ValueAnimator.ofInt(0, 255); 122 containerBackground.addUpdateListener( 123 animation -> getBackground().setAlpha((Integer) animation.getAnimatedValue())); 124 containerBackground.setDuration(SHOW_CONTAINER_ALPHA_DURATION); 125 containerBackground.setInterpolator(LINEAR); 126 127 ObjectAnimator containerSize = ObjectAnimator.ofFloat(this, SCALE_Y, 1f); 128 containerSize.setDuration(SHOW_CONTAINER_SCALE_DURATION); 129 containerSize.setInterpolator(EMPHASIZED_DECELERATE); 130 131 // Animate the contents 132 ObjectAnimator textAlpha = ObjectAnimator.ofFloat(mText, ALPHA, 1); 133 ObjectAnimator buttonAlpha = ObjectAnimator.ofFloat(mCloseButton, ALPHA, 1); 134 AnimatorSet contentAlpha = new AnimatorSet(); 135 contentAlpha.playTogether(textAlpha, buttonAlpha); 136 contentAlpha.setStartDelay(SHOW_CONTENT_ALPHA_DELAY); 137 contentAlpha.setDuration(SHOW_CONTENT_ALPHA_DURATION); 138 contentAlpha.setInterpolator(LINEAR); 139 140 // Start the animation 141 mShowAnimation = new AnimatorSet(); 142 mShowAnimation.addListener(new AnimatorListenerAdapter() { 143 @Override 144 public void onAnimationEnd(Animator animation) { 145 super.onAnimationEnd(animation); 146 mShowAnimation = null; 147 } 148 }); 149 mShowAnimation.playTogether(containerBackground, containerSize, contentAlpha); 150 mShowAnimation.start(); 151 } 152 153 /** 154 * Hide the floating view 155 */ hide()156 public void hide() { 157 if (mShowAnimation != null) { 158 mShowAnimation.cancel(); 159 } 160 mHideAnimation = ObjectAnimator.ofFloat(this, ALPHA, 0); 161 mHideAnimation.setDuration(HIDE_DURATION).setInterpolator(LINEAR); 162 mHideAnimation.addListener(new AnimatorListenerAdapter() { 163 @Override 164 public void onAnimationEnd(Animator animation) { 165 super.onAnimationEnd(animation); 166 mLauncher.getDragLayer().removeView(DesktopAppSelectView.this); 167 mHideAnimation = null; 168 } 169 }); 170 mHideAnimation.start(); 171 } 172 173 /** 174 * Add a callback that is called when close button is clicked 175 */ setOnCloseClickCallback(@ullable Runnable callback)176 public void setOnCloseClickCallback(@Nullable Runnable callback) { 177 mOnCloseCallback = callback; 178 } 179 } 180