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 
17 package com.android.wm.shell.windowdecor;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.annotation.ColorRes;
23 import android.app.ActivityManager.RunningTaskInfo;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.graphics.PixelFormat;
27 import android.graphics.Rect;
28 import android.graphics.drawable.Drawable;
29 import android.view.Display;
30 import android.view.LayoutInflater;
31 import android.view.SurfaceControl;
32 import android.view.SurfaceControlViewHost;
33 import android.view.View;
34 import android.view.WindowManager;
35 import android.view.WindowlessWindowManager;
36 import android.widget.ImageView;
37 import android.window.TaskConstants;
38 
39 import com.android.wm.shell.R;
40 
41 import java.util.function.Supplier;
42 
43 /**
44  * Creates and updates a veil that covers task contents on resize.
45  */
46 public class ResizeVeil {
47     private static final int RESIZE_ALPHA_DURATION = 100;
48     private final Context mContext;
49     private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
50     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
51     private final Drawable mAppIcon;
52     private SurfaceControl mParentSurface;
53     private SurfaceControl mVeilSurface;
54     private final RunningTaskInfo mTaskInfo;
55     private SurfaceControlViewHost mViewHost;
56     private final Display mDisplay;
57 
ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier)58     public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
59             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
60             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
61         mContext = context;
62         mAppIcon = appIcon;
63         mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
64         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
65         mTaskInfo = taskInfo;
66         mDisplay = display;
67         setupResizeVeil();
68     }
69 
70     /**
71      * Create the veil in its default invisible state.
72      */
setupResizeVeil()73     private void setupResizeVeil() {
74         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
75         final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
76         mVeilSurface = builder
77                 .setName("Resize veil of Task= " + mTaskInfo.taskId)
78                 .setContainerLayer()
79                 .build();
80         View v = LayoutInflater.from(mContext)
81                 .inflate(R.layout.desktop_mode_resize_veil, null);
82 
83         t.setPosition(mVeilSurface, 0, 0)
84             .setLayer(mVeilSurface, TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL)
85             .apply();
86         Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
87         final WindowManager.LayoutParams lp =
88                 new WindowManager.LayoutParams(taskBounds.width(),
89                         taskBounds.height(),
90                         WindowManager.LayoutParams.TYPE_APPLICATION,
91                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
92                         PixelFormat.TRANSPARENT);
93         lp.setTitle("Resize veil of Task=" + mTaskInfo.taskId);
94         lp.setTrustedOverlay();
95         WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
96                 mVeilSurface, null /* hostInputToken */);
97         mViewHost = new SurfaceControlViewHost(mContext, mDisplay, windowManager, "ResizeVeil");
98         mViewHost.setView(v, lp);
99 
100         final ImageView appIcon = mViewHost.getView().findViewById(R.id.veil_application_icon);
101         appIcon.setImageDrawable(mAppIcon);
102     }
103 
104     /**
105      * Shows the veil surface/view.
106      *
107      * @param t the transaction to apply in sync with the veil draw
108      * @param parentSurface the surface that the veil should be a child of
109      * @param taskBounds the bounds of the task that owns the veil
110      * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
111      *               immediately
112      */
showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface, Rect taskBounds, boolean fadeIn)113     public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
114             Rect taskBounds, boolean fadeIn) {
115         // Parent surface can change, ensure it is up to date.
116         if (!parentSurface.equals(mParentSurface)) {
117             t.reparent(mVeilSurface, parentSurface);
118             mParentSurface = parentSurface;
119         }
120 
121         int backgroundColorId = getBackgroundColorId();
122         mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
123 
124         relayout(taskBounds, t);
125         if (fadeIn) {
126             final ValueAnimator animator = new ValueAnimator();
127             animator.setFloatValues(0f, 1f);
128             animator.setDuration(RESIZE_ALPHA_DURATION);
129             animator.addUpdateListener(animation -> {
130                 t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
131                 t.apply();
132             });
133 
134             t.show(mVeilSurface)
135                     .addTransactionCommittedListener(
136                             mContext.getMainExecutor(), () -> animator.start())
137                     .setAlpha(mVeilSurface, 0);
138         } else {
139             // Show the veil immediately at full opacity.
140             t.show(mVeilSurface).setAlpha(mVeilSurface, 1);
141         }
142         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
143     }
144 
145     /**
146      * Animate veil's alpha to 1, fading it in.
147      */
showVeil(SurfaceControl parentSurface, Rect taskBounds)148     public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
149         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
150         showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
151     }
152 
153     /**
154      * Update veil bounds to match bounds changes.
155      * @param newBounds bounds to update veil to.
156      */
relayout(Rect newBounds, SurfaceControl.Transaction t)157     private void relayout(Rect newBounds, SurfaceControl.Transaction t) {
158         mViewHost.relayout(newBounds.width(), newBounds.height());
159         t.setWindowCrop(mVeilSurface, newBounds.width(), newBounds.height());
160         t.setPosition(mParentSurface, newBounds.left, newBounds.top);
161         t.setWindowCrop(mParentSurface, newBounds.width(), newBounds.height());
162     }
163 
164     /**
165      * Calls relayout to update task and veil bounds.
166      * @param newBounds bounds to update veil to.
167      */
updateResizeVeil(Rect newBounds)168     public void updateResizeVeil(Rect newBounds) {
169         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
170         updateResizeVeil(t, newBounds);
171     }
172 
173     /**
174      * Calls relayout to update task and veil bounds.
175      *
176      * @param t a transaction to be applied in sync with the veil draw.
177      * @param newBounds bounds to update veil to.
178      */
updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds)179     public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
180         relayout(newBounds, t);
181         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
182     }
183 
184     /**
185      * Animate veil's alpha to 0, fading it out.
186      */
hideVeil()187     public void hideVeil() {
188         final ValueAnimator animator = new ValueAnimator();
189         animator.setFloatValues(1, 0);
190         animator.setDuration(RESIZE_ALPHA_DURATION);
191         animator.addUpdateListener(animation -> {
192             SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
193             t.setAlpha(mVeilSurface, 1 - animator.getAnimatedFraction());
194             t.apply();
195         });
196         animator.addListener(new AnimatorListenerAdapter() {
197             @Override
198             public void onAnimationEnd(Animator animation) {
199                 SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
200                 t.hide(mVeilSurface);
201                 t.apply();
202             }
203         });
204         animator.start();
205     }
206 
207     @ColorRes
getBackgroundColorId()208     private int getBackgroundColorId() {
209         Configuration configuration = mContext.getResources().getConfiguration();
210         if ((configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
211                 == Configuration.UI_MODE_NIGHT_YES) {
212             return R.color.desktop_mode_resize_veil_dark;
213         } else {
214             return R.color.desktop_mode_resize_veil_light;
215         }
216     }
217 
218     /**
219      * Dispose of veil when it is no longer needed, likely on close of its container decor.
220      */
dispose()221     void dispose() {
222         if (mViewHost != null) {
223             mViewHost.release();
224             mViewHost = null;
225         }
226         if (mVeilSurface != null) {
227             final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
228             t.remove(mVeilSurface);
229             mVeilSurface = null;
230             t.apply();
231         }
232     }
233 }
234