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