1 /* 2 * Copyright (C) 2016 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 package com.android.systemui.recents.tv.views; 17 18 import android.annotation.Nullable; 19 import android.app.ActivityOptions; 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.Rect; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.IRemoteCallback; 26 import android.os.RemoteException; 27 import android.util.Log; 28 import android.view.WindowManagerGlobal; 29 import com.android.internal.annotations.GuardedBy; 30 import com.android.systemui.recents.Recents; 31 import com.android.systemui.recents.events.EventBus; 32 import com.android.systemui.recents.events.activity.*; 33 import com.android.systemui.recents.misc.SystemServicesProxy; 34 import com.android.systemui.recents.model.Task; 35 import com.android.systemui.recents.model.TaskStack; 36 37 38 public class RecentsTvTransitionHelper { 39 private static final String TAG = "RecentsTvTransitionHelper"; 40 41 private Context mContext; 42 private Handler mHandler; 43 RecentsTvTransitionHelper(Context context, Handler handler)44 public RecentsTvTransitionHelper(Context context, Handler handler) { 45 mContext = context; 46 mHandler = handler; 47 } 48 launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, final TaskStackHorizontalGridView stackView, final TaskCardView taskView, final Rect bounds, int destinationStack)49 public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, 50 final TaskStackHorizontalGridView stackView, final TaskCardView taskView, 51 final Rect bounds, int destinationStack) { 52 final ActivityOptions opts = ActivityOptions.makeBasic(); 53 if (bounds != null) { 54 opts.setLaunchBounds(bounds.isEmpty() ? null : bounds); 55 } 56 57 final ActivityOptions.OnAnimationStartedListener animStartedListener; 58 if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && 59 task.thumbnail.getHeight() > 0) { 60 animStartedListener = new ActivityOptions.OnAnimationStartedListener() { 61 @Override 62 public void onAnimationStarted() { 63 // If we are launching into another task, cancel the previous task's 64 // window transition 65 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); 66 EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); 67 } 68 }; 69 } else { 70 // This is only the case if the task is not on screen (scrolled offscreen for example) 71 animStartedListener = new ActivityOptions.OnAnimationStartedListener() { 72 @Override 73 public void onAnimationStarted() { 74 EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); 75 } 76 }; 77 } 78 79 if (taskView == null) { 80 // If there is no task view, then we do not need to worry about animating out occluding 81 // task views, and we can launch immediately 82 startTaskActivity(stack, task, taskView, opts, animStartedListener); 83 } else { 84 LaunchTvTaskStartedEvent launchStartedEvent = new LaunchTvTaskStartedEvent(taskView); 85 EventBus.getDefault().send(launchStartedEvent); 86 startTaskActivity(stack, task, taskView, opts, animStartedListener); 87 } 88 } 89 startTaskActivity(TaskStack stack, Task task, @Nullable TaskCardView taskView, ActivityOptions opts,final ActivityOptions.OnAnimationStartedListener animStartedListener)90 private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskCardView taskView, 91 ActivityOptions opts,final ActivityOptions.OnAnimationStartedListener animStartedListener) { 92 SystemServicesProxy ssp = Recents.getSystemServices(); 93 if (ssp.startActivityFromRecents(mContext, task.key, task.title, opts)) { 94 // Keep track of the index of the task launch 95 int taskIndexFromFront = 0; 96 int taskIndex = stack.indexOfStackTask(task); 97 if (taskIndex > -1) { 98 taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; 99 } 100 EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront)); 101 } else { 102 // Keep track of failed launches 103 EventBus.getDefault().send(new LaunchTaskFailedEvent()); 104 } 105 106 Rect taskRect = taskView.getFocusedThumbnailRect(); 107 // Check both the rect and the thumbnail for null. The rect can be null if the user 108 // decides to disallow animations, so automatic scrolling does not happen properly. 109 110 // The thumbnail can be null if the app was partially launched on TV. In this case 111 // we do not override the transition. 112 if (taskRect == null || task.thumbnail == null) { 113 return; 114 } 115 116 IRemoteCallback.Stub callback = null; 117 if (animStartedListener != null) { 118 callback = new IRemoteCallback.Stub() { 119 @Override 120 public void sendResult(Bundle data) throws RemoteException { 121 mHandler.post(new Runnable() { 122 @Override 123 public void run() { 124 if (animStartedListener != null) { 125 animStartedListener.onAnimationStarted(); 126 } 127 } 128 }); 129 } 130 }; 131 } 132 try { 133 Bitmap thumbnail = Bitmap.createScaledBitmap(task.thumbnail, taskRect.width(), 134 taskRect.height(), false); 135 WindowManagerGlobal.getWindowManagerService() 136 .overridePendingAppTransitionAspectScaledThumb(thumbnail, taskRect.left, 137 taskRect.top, taskRect.width(), taskRect.height(), callback, true); 138 } catch (RemoteException e) { 139 Log.w(TAG, "Failed to override transition: " + e); 140 } 141 } 142 } 143