blob: f3201d0346b422104bffa3fc37ec97a0427063c6 [file] [log] [blame]
Sid Soundararajanb58c46a2016-01-26 15:39:27 -08001/*
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 */
16package com.android.systemui.recents.tv;
17
18import android.app.Activity;
19import android.app.ActivityOptions;
20import android.content.Intent;
21import android.os.Bundle;
22import android.os.UserHandle;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080023import android.util.Log;
24import android.view.KeyEvent;
25import android.view.View;
26import android.view.ViewTreeObserver.OnPreDrawListener;
27import android.view.WindowManager;
Winsonc0d70582016-01-29 10:24:39 -080028
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080029import com.android.systemui.R;
Winsonc0d70582016-01-29 10:24:39 -080030import com.android.systemui.recents.Recents;
31import com.android.systemui.recents.RecentsActivityLaunchState;
32import com.android.systemui.recents.RecentsConfiguration;
33import com.android.systemui.recents.RecentsImpl;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080034import com.android.systemui.recents.events.EventBus;
35import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
36import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
37import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
38import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
39import com.android.systemui.recents.events.activity.HideRecentsEvent;
40import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
41import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
42import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
43import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
44import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
45import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
46import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
47import com.android.systemui.recents.events.ui.UserInteractionEvent;
48import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
49import com.android.systemui.recents.misc.SystemServicesProxy;
50import com.android.systemui.recents.model.RecentsPackageMonitor;
51import com.android.systemui.recents.model.RecentsTaskLoadPlan;
52import com.android.systemui.recents.model.RecentsTaskLoader;
53import com.android.systemui.recents.model.Task;
54import com.android.systemui.recents.model.TaskStack;
55import com.android.systemui.recents.tv.views.RecentsTvView;
56import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
57import com.android.systemui.statusbar.BaseStatusBar;
58import com.android.systemui.tv.pip.PipManager;
59
60import java.util.ArrayList;
61/**
62 * The main TV recents activity started by the RecentsImpl.
63 */
64public class RecentsTvActivity extends Activity implements OnPreDrawListener {
65 private final static String TAG = "RecentsTvActivity";
66 private final static boolean DEBUG = false;
67
68 public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
69
70 private boolean mFinishedOnStartup;
71 private RecentsPackageMonitor mPackageMonitor;
72 private long mLastTabKeyEventTime;
73 private boolean mIgnoreAltTabRelease;
74
75 private RecentsTvView mRecentsView;
76 private TaskStackHorizontalViewAdapter mTaskStackViewAdapter;
77 private FinishRecentsRunnable mFinishLaunchHomeRunnable;
78
79
80 /**
81 * A common Runnable to finish Recents by launching Home with an animation depending on the
82 * last activity launch state. Generally we always launch home when we exit Recents rather than
83 * just finishing the activity since we don't know what is behind Recents in the task stack.
84 */
85 class FinishRecentsRunnable implements Runnable {
86 Intent mLaunchIntent;
87
88 /**
89 * Creates a finish runnable that starts the specified intent.
90 */
91 public FinishRecentsRunnable(Intent launchIntent) {
92 mLaunchIntent = launchIntent;
93 }
94
95 @Override
96 public void run() {
97 try {
98 RecentsActivityLaunchState launchState =
99 Recents.getConfiguration().getLaunchState();
100 ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsTvActivity.this,
101 launchState.launchedFromSearchHome ?
102 R.anim.recents_to_search_launcher_enter :
103 R.anim.recents_to_launcher_enter,
104 launchState.launchedFromSearchHome ?
105 R.anim.recents_to_search_launcher_exit :
106 R.anim.recents_to_launcher_exit);
107 startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
108 } catch (Exception e) {
109 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
110 }
111 }
112 }
113
114 private void updateRecentsTasks() {
115 RecentsTaskLoader loader = Recents.getTaskLoader();
116 RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
117 if (plan == null) {
118 plan = loader.createLoadPlan(this);
119 }
120
121 RecentsConfiguration config = Recents.getConfiguration();
122 RecentsActivityLaunchState launchState = config.getLaunchState();
123 if (!plan.hasTasks()) {
124 loader.preloadTasks(plan, -1, launchState.launchedFromHome);
125 }
126 TaskStack stack = plan.getTaskStack();
127 RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
128 loadOpts.runningTaskId = launchState.launchedToTaskId;
129 loadOpts.numVisibleTasks = stack.getStackTaskCount();
130 loadOpts.numVisibleTaskThumbnails = stack.getStackTaskCount();
131 loader.loadTasks(this, plan, loadOpts);
132
133
134 mRecentsView.setTaskStack(stack);
135 if (mTaskStackViewAdapter == null) {
136 mTaskStackViewAdapter = new TaskStackHorizontalViewAdapter(stack.getStackTasks());
137 mRecentsView.setTaskStackViewAdapter(mTaskStackViewAdapter);
138 } else {
139 mTaskStackViewAdapter.setNewStackTasks(stack.getStackTasks());
140 }
141
142 if (launchState.launchedToTaskId != -1) {
143 ArrayList<Task> tasks = stack.getStackTasks();
144 int taskCount = tasks.size();
145 for (int i = 0; i < taskCount; i++) {
146 Task t = tasks.get(i);
147 if (t.key.id == launchState.launchedToTaskId) {
148 t.isLaunchTarget = true;
149 break;
150 }
151 }
152 }
153 }
154
155 boolean dismissRecentsToLaunchTargetTaskOrHome() {
156 SystemServicesProxy ssp = Recents.getSystemServices();
157 if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
158 // If we have a focused Task, launch that Task now
159 if (mRecentsView.launchPreviousTask()) return true;
160 // If none of the other cases apply, then just go Home
161 dismissRecentsToHome(true /* animateTaskViews */);
162 }
163 return false;
164 }
165
166 boolean dismissRecentsToFocusedTaskOrHome() {
167 SystemServicesProxy ssp = Recents.getSystemServices();
168 if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
169 // If we have a focused Task, launch that Task now
170 if (mRecentsView.launchFocusedTask()) return true;
171 // If none of the other cases apply, then just go Home
172 dismissRecentsToHome(true /* animateTaskViews */);
173 return true;
174 }
175 return false;
176 }
177
178 void dismissRecentsToHome(boolean animateTaskViews) {
179 DismissRecentsToHomeAnimationStarted dismissEvent =
180 new DismissRecentsToHomeAnimationStarted(animateTaskViews);
181 dismissEvent.addPostAnimationCallback(mFinishLaunchHomeRunnable);
182 dismissEvent.addPostAnimationCallback(new Runnable() {
183 @Override
184 public void run() {
185 Recents.getSystemServices().sendCloseSystemWindows(
186 BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
187 }
188 });
189 EventBus.getDefault().send(dismissEvent);
190 }
191
192 boolean dismissRecentsToHomeIfVisible(boolean animated) {
193 SystemServicesProxy ssp = Recents.getSystemServices();
194 if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
195 // Return to Home
196 dismissRecentsToHome(animated);
197 return true;
198 }
199 return false;
200 }
201
202 @Override
203 public void onCreate(Bundle savedInstanceState) {
204 super.onCreate(savedInstanceState);
205 mFinishedOnStartup = false;
206
207 // In the case that the activity starts up before the Recents component has initialized
208 // (usually when debugging/pushing the SysUI apk), just finish this activity.
209 SystemServicesProxy ssp = Recents.getSystemServices();
210 if (ssp == null) {
211 mFinishedOnStartup = true;
212 finish();
213 return;
214 }
215
216 // Register this activity with the event bus
217 EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
218
219 mPackageMonitor = new RecentsPackageMonitor();
220 mPackageMonitor.register(this);
221
222 // Set the Recents layout
223 setContentView(R.layout.recents_on_tv);
224
225 mRecentsView = (RecentsTvView) findViewById(R.id.recents_view);
226 mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
227 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
228 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
229
230 getWindow().getAttributes().privateFlags |=
231 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
232
233 // Create the home intent runnable
234 Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
235 homeIntent.addCategory(Intent.CATEGORY_HOME);
236 homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
237 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
238 mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
239 }
240
241 @Override
242 protected void onNewIntent(Intent intent) {
243 super.onNewIntent(intent);
244 setIntent(intent);
245 }
246
247 @Override
248 protected void onStart() {
249 super.onStart();
250
251 // Update the recent tasks
252 updateRecentsTasks();
253
254 // If this is a new instance from a configuration change, then we have to manually trigger
255 // the enter animation state, or if recents was relaunched by AM, without going through
256 // the normal mechanisms
257 RecentsConfiguration config = Recents.getConfiguration();
258 RecentsActivityLaunchState launchState = config.getLaunchState();
259 boolean wasLaunchedByAm = !launchState.launchedFromHome &&
260 !launchState.launchedFromAppWithThumbnail;
261 if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
262 EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
263 }
264
265 // Notify that recents is now visible
266 SystemServicesProxy ssp = Recents.getSystemServices();
267 EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
268 }
269
270 @Override
271 public void onEnterAnimationComplete() {
272 super.onEnterAnimationComplete();
273 EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
274 }
275
276 @Override
277 protected void onStop() {
278 super.onStop();
279
280 mIgnoreAltTabRelease = false;
281 // Notify that recents is now hidden
282 SystemServicesProxy ssp = Recents.getSystemServices();
283 EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
284
285 // Workaround for b/22542869, if the RecentsActivity is started again, but without going
286 // through SystemUI, we need to reset the config launch flags to ensure that we do not
287 // wait on the system to send a signal that was never queued.
288 RecentsConfiguration config = Recents.getConfiguration();
289 RecentsActivityLaunchState launchState = config.getLaunchState();
Jorim Jaggie161f082016-02-05 14:26:16 -0800290 launchState.reset();
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800291 }
292
293 @Override
294 protected void onDestroy() {
295 super.onDestroy();
296
297 // In the case that the activity finished on startup, just skip the unregistration below
298 if (mFinishedOnStartup) {
299 return;
300 }
301
302 // Unregister any broadcast receivers for the task loader
303 mPackageMonitor.unregister();
304
305 EventBus.getDefault().unregister(this);
306 }
307
308 @Override
309 public void onTrimMemory(int level) {
310 RecentsTaskLoader loader = Recents.getTaskLoader();
311 if (loader != null) {
312 loader.onTrimMemory(level);
313 }
314 }
315
316 @Override
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800317 public boolean onKeyDown(int keyCode, KeyEvent event) {
318 switch (keyCode) {
319 case KeyEvent.KEYCODE_DPAD_UP: {
320 SystemServicesProxy ssp = Recents.getSystemServices();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800321 PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_MENU);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800322 ssp.focusPinnedStack();
323 return true;
324 }
325 case KeyEvent.KEYCODE_DPAD_DOWN: {
326 SystemServicesProxy ssp = Recents.getSystemServices();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800327 PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800328 ssp.focusHomeStack();
329 return true;
330 }
331 case KeyEvent.KEYCODE_DEL:
332 case KeyEvent.KEYCODE_FORWARD_DEL: {
333 EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
334 return true;
335 }
336 default:
337 break;
338 }
339 return super.onKeyDown(keyCode, event);
340 }
341
342 @Override
343 public void onUserInteraction() {
344 EventBus.getDefault().send(new UserInteractionEvent());
345 }
346
347 @Override
348 public void onBackPressed() {
349 // Back behaves like the recents button so just trigger a toggle event
350 EventBus.getDefault().send(new ToggleRecentsEvent());
351 }
352
353 /**** EventBus events ****/
354
355 public final void onBusEvent(ToggleRecentsEvent event) {
356 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
357 if (launchState.launchedFromHome) {
358 dismissRecentsToHome(true /* animateTaskViews */);
359 } else {
360 dismissRecentsToLaunchTargetTaskOrHome();
361 }
362 }
363
364 public final void onBusEvent(HideRecentsEvent event) {
365 if (event.triggeredFromAltTab) {
366 // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
367 if (!mIgnoreAltTabRelease) {
368 dismissRecentsToFocusedTaskOrHome();
369 }
370 } else if (event.triggeredFromHomeKey) {
371 dismissRecentsToHome(true /* animateTaskViews */);
372 } else {
373 // Do nothing
374 }
375 }
376
377 public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
378 EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
379 mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
380 mRecentsView.invalidate();
381 }
382
383 public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
384 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
385 int launchToTaskId = launchState.launchedToTaskId;
386 if (launchToTaskId != -1 &&
387 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
388 SystemServicesProxy ssp = Recents.getSystemServices();
389 ssp.cancelWindowTransition(launchState.launchedToTaskId);
390 ssp.cancelThumbnailTransition(getTaskId());
391 }
392 }
393
394 public final void onBusEvent(DeleteTaskDataEvent event) {
395 // Remove any stored data from the loader
396 RecentsTaskLoader loader = Recents.getTaskLoader();
397 loader.deleteTaskData(event.task, false);
398
399 // Remove the task from activity manager
400 SystemServicesProxy ssp = Recents.getSystemServices();
401 ssp.removeTask(event.task.key.id);
402 }
403
404 public final void onBusEvent(AllTaskViewsDismissedEvent event) {
405 SystemServicesProxy ssp = Recents.getSystemServices();
406 if (ssp.hasDockedTask()) {
407 mRecentsView.showEmptyView();
408 } else {
409 // Just go straight home (no animation necessary because there are no more task views)
410 dismissRecentsToHome(false /* animateTaskViews */);
411 }
412 }
413
414 public final void onBusEvent(LaunchTaskFailedEvent event) {
415 // Return to Home
416 dismissRecentsToHome(true /* animateTaskViews */);
417 }
418
419 @Override
420 public boolean onPreDraw() {
421 mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
422 // We post to make sure that this information is delivered after this traversals is
423 // finished.
424 mRecentsView.post(new Runnable() {
425 @Override
426 public void run() {
427 Recents.getSystemServices().endProlongedAnimations();
428 }
429 });
430 return true;
431 }
432}