1 package com.android.quickstep.views; 2 3 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO; 4 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 5 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 6 7 import android.content.Context; 8 import android.util.AttributeSet; 9 import android.view.MotionEvent; 10 11 import androidx.annotation.NonNull; 12 import androidx.annotation.Nullable; 13 14 import com.android.launcher3.DeviceProfile; 15 import com.android.launcher3.R; 16 import com.android.launcher3.util.RunnableList; 17 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; 18 import com.android.launcher3.util.TransformingTouchDelegate; 19 import com.android.quickstep.RecentsModel; 20 import com.android.quickstep.TaskIconCache; 21 import com.android.quickstep.TaskThumbnailCache; 22 import com.android.quickstep.util.CancellableTask; 23 import com.android.quickstep.util.RecentsOrientedState; 24 import com.android.systemui.shared.recents.model.Task; 25 import com.android.systemui.shared.recents.model.ThumbnailData; 26 27 import java.util.HashMap; 28 import java.util.function.Consumer; 29 30 /** 31 * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks 32 * 33 * That's right. If you call within the next 5 minutes we'll go ahead and double your order and 34 * send you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House. 35 * And not only that, we'll even clean up your thumbnail request if you don't like it. 36 * All the benefits of one TaskView, except DOUBLED! 37 * 38 * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included). 39 */ 40 public class GroupedTaskView extends TaskView { 41 42 @Nullable 43 private Task mSecondaryTask; 44 private TaskThumbnailView mSnapshotView2; 45 private IconView mIconView2; 46 @Nullable 47 private CancellableTask<ThumbnailData> mThumbnailLoadRequest2; 48 @Nullable 49 private CancellableTask mIconLoadRequest2; 50 private final float[] mIcon2CenterCoords = new float[2]; 51 private TransformingTouchDelegate mIcon2TouchDelegate; 52 @Nullable private StagedSplitBounds mSplitBoundsConfig; 53 private final DigitalWellBeingToast mDigitalWellBeingToast2; 54 55 GroupedTaskView(Context context)56 public GroupedTaskView(Context context) { 57 this(context, null); 58 } 59 GroupedTaskView(Context context, AttributeSet attrs)60 public GroupedTaskView(Context context, AttributeSet attrs) { 61 this(context, attrs, 0); 62 } 63 GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr)64 public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) { 65 super(context, attrs, defStyleAttr); 66 mDigitalWellBeingToast2 = new DigitalWellBeingToast(mActivity, this); 67 } 68 69 @Override onFinishInflate()70 protected void onFinishInflate() { 71 super.onFinishInflate(); 72 mSnapshotView2 = findViewById(R.id.bottomright_snapshot); 73 mIconView2 = findViewById(R.id.bottomRight_icon); 74 mIcon2TouchDelegate = new TransformingTouchDelegate(mIconView2); 75 } 76 bind(Task primary, Task secondary, RecentsOrientedState orientedState, @Nullable StagedSplitBounds splitBoundsConfig)77 public void bind(Task primary, Task secondary, RecentsOrientedState orientedState, 78 @Nullable StagedSplitBounds splitBoundsConfig) { 79 super.bind(primary, orientedState); 80 mSecondaryTask = secondary; 81 mTaskIdContainer[1] = secondary.key.id; 82 mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2, 83 mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT); 84 mTaskIdAttributeContainer[0].setStagePosition(STAGE_POSITION_TOP_OR_LEFT); 85 mSnapshotView2.bind(secondary); 86 mSplitBoundsConfig = splitBoundsConfig; 87 } 88 89 @Override onTaskListVisibilityChanged(boolean visible, int changes)90 public void onTaskListVisibilityChanged(boolean visible, int changes) { 91 super.onTaskListVisibilityChanged(visible, changes); 92 if (visible) { 93 RecentsModel model = RecentsModel.INSTANCE.get(getContext()); 94 TaskThumbnailCache thumbnailCache = model.getThumbnailCache(); 95 TaskIconCache iconCache = model.getIconCache(); 96 97 if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { 98 mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(mSecondaryTask, 99 thumbnailData -> mSnapshotView2.setThumbnail( 100 mSecondaryTask, thumbnailData 101 )); 102 } 103 104 if (needsUpdate(changes, FLAG_UPDATE_ICON)) { 105 mIconLoadRequest2 = iconCache.updateIconInBackground(mSecondaryTask, 106 (task) -> { 107 setIcon(mIconView2, task.icon); 108 mDigitalWellBeingToast2.initialize(mSecondaryTask); 109 mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig); 110 mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig); 111 }); 112 } 113 } else { 114 if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { 115 mSnapshotView2.setThumbnail(null, null); 116 // Reset the task thumbnail reference as well (it will be fetched from the cache or 117 // reloaded next time we need it) 118 mSecondaryTask.thumbnail = null; 119 } 120 if (needsUpdate(changes, FLAG_UPDATE_ICON)) { 121 setIcon(mIconView2, null); 122 } 123 } 124 } 125 updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds)126 public void updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds) { 127 mSplitBoundsConfig = stagedSplitBounds; 128 invalidate(); 129 } 130 getSplitRatio()131 public float getSplitRatio() { 132 if (mSplitBoundsConfig != null) { 133 return mSplitBoundsConfig.appsStackedVertically 134 ? mSplitBoundsConfig.topTaskPercent : mSplitBoundsConfig.leftTaskPercent; 135 } 136 return DEFAULT_SPLIT_RATIO; 137 } 138 139 @Override offerTouchToChildren(MotionEvent event)140 public boolean offerTouchToChildren(MotionEvent event) { 141 computeAndSetIconTouchDelegate(mIconView2, mIcon2CenterCoords, mIcon2TouchDelegate); 142 if (mIcon2TouchDelegate.onTouchEvent(event)) { 143 return true; 144 } 145 146 return super.offerTouchToChildren(event); 147 } 148 149 @Override cancelPendingLoadTasks()150 protected void cancelPendingLoadTasks() { 151 super.cancelPendingLoadTasks(); 152 if (mThumbnailLoadRequest2 != null) { 153 mThumbnailLoadRequest2.cancel(); 154 mThumbnailLoadRequest2 = null; 155 } 156 if (mIconLoadRequest2 != null) { 157 mIconLoadRequest2.cancel(); 158 mIconLoadRequest2 = null; 159 } 160 } 161 162 @Nullable 163 @Override launchTaskAnimated()164 public RunnableList launchTaskAnimated() { 165 if (mTask == null || mSecondaryTask == null) { 166 return null; 167 } 168 169 RunnableList endCallback = new RunnableList(); 170 RecentsView recentsView = getRecentsView(); 171 // Callbacks run from remote animation when recents animation not currently running 172 recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/, 173 success -> endCallback.executeAllAndDestroy(), 174 false /* freezeTaskList */); 175 176 // Callbacks get run from recentsView for case when recents animation already running 177 recentsView.addSideTaskLaunchCallback(endCallback); 178 return endCallback; 179 } 180 181 @Override launchTask(@onNull Consumer<Boolean> callback, boolean freezeTaskList)182 public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) { 183 getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask, 184 STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, 185 getSplitRatio()); 186 } 187 188 @Override refreshThumbnails(@ullable HashMap<Integer, ThumbnailData> thumbnailDatas)189 void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) { 190 super.refreshThumbnails(thumbnailDatas); 191 if (mSecondaryTask != null && thumbnailDatas != null) { 192 final ThumbnailData thumbnailData = thumbnailDatas.get(mSecondaryTask.key.id); 193 if (thumbnailData != null) { 194 mSnapshotView2.setThumbnail(mSecondaryTask, thumbnailData); 195 return; 196 } 197 } 198 199 mSnapshotView2.refresh(); 200 } 201 202 @Override getThumbnails()203 public TaskThumbnailView[] getThumbnails() { 204 return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2}; 205 } 206 207 @Override onRecycle()208 public void onRecycle() { 209 super.onRecycle(); 210 mSnapshotView2.setThumbnail(mSecondaryTask, null); 211 mSplitBoundsConfig = null; 212 } 213 214 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)215 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 216 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 217 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 218 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 219 setMeasuredDimension(widthSize, heightSize); 220 if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) { 221 return; 222 } 223 getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView, 224 mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig, 225 mActivity.getDeviceProfile()); 226 updateIconPlacement(); 227 } 228 229 @Override setOverlayEnabled(boolean overlayEnabled)230 public void setOverlayEnabled(boolean overlayEnabled) { 231 super.setOverlayEnabled(overlayEnabled); 232 mSnapshotView2.setOverlayEnabled(overlayEnabled); 233 } 234 235 @Override setOrientationState(RecentsOrientedState orientationState)236 public void setOrientationState(RecentsOrientedState orientationState) { 237 super.setOrientationState(orientationState); 238 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 239 boolean isGridTask = deviceProfile.overviewShowAsGrid && !isFocusedTask(); 240 int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx 241 : deviceProfile.overviewTaskIconDrawableSizePx; 242 mIconView2.setDrawableSize(iconDrawableSize, iconDrawableSize); 243 mIconView2.setRotation(getPagedOrientationHandler().getDegreesRotated()); 244 updateIconPlacement(); 245 } 246 updateIconPlacement()247 private void updateIconPlacement() { 248 if (mSplitBoundsConfig == null) { 249 return; 250 } 251 252 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 253 int taskIconHeight = deviceProfile.overviewTaskIconSizePx; 254 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 255 256 getPagedOrientationHandler().setSplitIconParams(mIconView, mIconView2, 257 taskIconHeight, mSnapshotView.getMeasuredWidth(), mSnapshotView.getMeasuredHeight(), 258 isRtl, deviceProfile, mSplitBoundsConfig); 259 } 260 261 @Override updateSnapshotRadius()262 protected void updateSnapshotRadius() { 263 super.updateSnapshotRadius(); 264 mSnapshotView2.setFullscreenParams(mCurrentFullscreenParams); 265 } 266 267 @Override setIconAndDimTransitionProgress(float progress, boolean invert)268 protected void setIconAndDimTransitionProgress(float progress, boolean invert) { 269 super.setIconAndDimTransitionProgress(progress, invert); 270 // Value set by super call 271 float scale = mIconView.getAlpha(); 272 mIconView2.setAlpha(scale); 273 mDigitalWellBeingToast2.updateBannerOffset(1f - scale, 274 mCurrentFullscreenParams.mCurrentDrawnInsets.top 275 + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom); 276 } 277 278 @Override setColorTint(float amount, int tintColor)279 public void setColorTint(float amount, int tintColor) { 280 super.setColorTint(amount, tintColor); 281 mIconView2.setIconColorTint(tintColor, amount); 282 mSnapshotView2.setDimAlpha(amount); 283 mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount); 284 } 285 } 286