1 /*
2  * Copyright (C) 2014 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.systemui.recents.views;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
24 
25 import android.content.Context;
26 import android.graphics.Bitmap;
27 import android.graphics.Rect;
28 import android.util.Log;
29 
30 import com.android.systemui.recents.LegacyRecentsImpl;
31 import com.android.systemui.recents.RecentsDebugFlags;
32 import com.android.systemui.shared.recents.model.Task;
33 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
34 import com.android.systemui.shared.recents.view.RecentsTransition;
35 
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
39 
40 /**
41  * A helper class to create the transition app animation specs to/from Recents
42  */
43 public class RecentsTransitionComposer {
44 
45     private static final String TAG = "RecentsTransitionComposer";
46 
47     private Context mContext;
48     private TaskViewTransform mTmpTransform = new TaskViewTransform();
49 
RecentsTransitionComposer(Context context)50     public RecentsTransitionComposer(Context context) {
51         mContext = context;
52     }
53 
54     /**
55      * Composes a single animation spec for the given {@link TaskView}
56      */
composeAnimationSpec(TaskStackView stackView, TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap)57     private static AppTransitionAnimationSpecCompat composeAnimationSpec(TaskStackView stackView,
58             TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
59         Bitmap b = null;
60         if (addHeaderBitmap) {
61             b = composeHeaderBitmap(taskView, transform);
62             if (b == null) {
63                 return null;
64             }
65         }
66 
67         Rect taskRect = new Rect();
68         transform.rect.round(taskRect);
69         // Disable in for low ram devices because each task does in Recents does not have fullscreen
70         // height (stackView height) and when transitioning to fullscreen app, the code below would
71         // force the task thumbnail to full stackView height immediately causing the transition
72         // jarring.
73         if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice && taskView.getTask() !=
74                 stackView.getStack().getFrontMostTask()) {
75             taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
76         }
77         return new AppTransitionAnimationSpecCompat(taskView.getTask().key.id, b, taskRect);
78     }
79 
80     /**
81      * Composes the transition spec when docking a task, which includes a full task bitmap.
82      */
composeDockAnimationSpec(TaskView taskView, Rect bounds)83     public List<AppTransitionAnimationSpecCompat> composeDockAnimationSpec(TaskView taskView,
84             Rect bounds) {
85         mTmpTransform.fillIn(taskView);
86         Task task = taskView.getTask();
87         Bitmap buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
88         return Collections.singletonList(new AppTransitionAnimationSpecCompat(task.key.id, buffer,
89                 bounds));
90     }
91 
92     /**
93      * Composes the animation specs for all the tasks in the target stack.
94      */
composeAnimationSpecs(final Task task, final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect)95     public List<AppTransitionAnimationSpecCompat> composeAnimationSpecs(final Task task,
96             final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
97         // Calculate the offscreen task rect (for tasks that are not backed by views)
98         TaskView taskView = stackView.getChildViewForTask(task);
99         TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
100         Rect offscreenTaskRect = new Rect();
101         stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
102 
103         // If this is a full screen stack, the transition will be towards the single, full screen
104         // task. We only need the transition spec for this task.
105 
106         // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
107         // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
108         if (windowingMode == WINDOWING_MODE_FULLSCREEN
109                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
110                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
111                 || activityType == ACTIVITY_TYPE_ASSISTANT
112                 || windowingMode == WINDOWING_MODE_UNDEFINED) {
113             List<AppTransitionAnimationSpecCompat> specs = new ArrayList<>();
114             if (taskView == null) {
115                 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
116             } else {
117                 mTmpTransform.fillIn(taskView);
118                 stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
119                 AppTransitionAnimationSpecCompat spec = composeAnimationSpec(stackView, taskView,
120                         mTmpTransform, true /* addHeaderBitmap */);
121                 if (spec != null) {
122                     specs.add(spec);
123                 }
124             }
125             return specs;
126         }
127         return Collections.emptyList();
128     }
129 
130     /**
131      * Composes a single animation spec for the given {@link Task}
132      */
composeOffscreenAnimationSpec(Task task, Rect taskRect)133     private static AppTransitionAnimationSpecCompat composeOffscreenAnimationSpec(Task task,
134             Rect taskRect) {
135         return new AppTransitionAnimationSpecCompat(task.key.id, null, taskRect);
136     }
137 
composeTaskBitmap(TaskView taskView, TaskViewTransform transform)138     public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
139         float scale = transform.scale;
140         int fromWidth = (int) (transform.rect.width() * scale);
141         int fromHeight = (int) (transform.rect.height() * scale);
142         if (fromWidth == 0 || fromHeight == 0) {
143             Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
144                     " at transform: " + transform);
145 
146             return RecentsTransition.drawViewIntoHardwareBitmap(1, 1, null, 1f, 0x00ffffff);
147         } else {
148             if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
149                 return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, null, 1f,
150                         0xFFff0000);
151             } else {
152                 return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, taskView,
153                         scale, 0);
154             }
155         }
156     }
157 
composeHeaderBitmap(TaskView taskView, TaskViewTransform transform)158     private static Bitmap composeHeaderBitmap(TaskView taskView,
159             TaskViewTransform transform) {
160         float scale = transform.scale;
161         int headerWidth = (int) (transform.rect.width());
162         int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
163         if (headerWidth == 0 || headerHeight == 0) {
164             return null;
165         }
166 
167         if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
168             return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, null, 1f,
169                     0xFFff0000);
170         } else {
171             return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight,
172                     taskView.mHeaderView, scale, 0);
173         }
174     }
175 }
176