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