blob: dae522f2c072d3a009d2b4daa8af5be33005eb13 [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;
Jaewan Kimc92a7d12016-02-15 17:33:25 -080021import android.graphics.Rect;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080022import android.os.Bundle;
23import android.os.UserHandle;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080024import android.util.Log;
25import android.view.KeyEvent;
26import android.view.View;
27import android.view.ViewTreeObserver.OnPreDrawListener;
28import android.view.WindowManager;
Jaewan Kimc92a7d12016-02-15 17:33:25 -080029import android.widget.FrameLayout.LayoutParams;
Winsonc0d70582016-01-29 10:24:39 -080030
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080031import com.android.systemui.R;
Winsonc0d70582016-01-29 10:24:39 -080032import com.android.systemui.recents.Recents;
33import com.android.systemui.recents.RecentsActivityLaunchState;
34import com.android.systemui.recents.RecentsConfiguration;
35import com.android.systemui.recents.RecentsImpl;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080036import com.android.systemui.recents.events.EventBus;
37import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
38import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
39import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
40import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
41import com.android.systemui.recents.events.activity.HideRecentsEvent;
42import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080043import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
44import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
45import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
46import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
47import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
48import com.android.systemui.recents.events.ui.UserInteractionEvent;
49import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
50import com.android.systemui.recents.misc.SystemServicesProxy;
51import com.android.systemui.recents.model.RecentsPackageMonitor;
52import com.android.systemui.recents.model.RecentsTaskLoadPlan;
53import com.android.systemui.recents.model.RecentsTaskLoader;
54import com.android.systemui.recents.model.Task;
55import com.android.systemui.recents.model.TaskStack;
56import com.android.systemui.recents.tv.views.RecentsTvView;
57import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
58import com.android.systemui.statusbar.BaseStatusBar;
59import com.android.systemui.tv.pip.PipManager;
60
61import java.util.ArrayList;
Sid Soundararajan1008cc22016-02-01 11:11:14 -080062import java.util.Collections;
63import java.util.List;
Jaewan Kimc92a7d12016-02-15 17:33:25 -080064
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080065/**
66 * The main TV recents activity started by the RecentsImpl.
67 */
68public class RecentsTvActivity extends Activity implements OnPreDrawListener {
69 private final static String TAG = "RecentsTvActivity";
70 private final static boolean DEBUG = false;
71
72 public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
73
74 private boolean mFinishedOnStartup;
75 private RecentsPackageMonitor mPackageMonitor;
76 private long mLastTabKeyEventTime;
77 private boolean mIgnoreAltTabRelease;
78
79 private RecentsTvView mRecentsView;
Jaewan Kimc92a7d12016-02-15 17:33:25 -080080 private View mPipView;
81 private View mPipShadeView;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080082 private TaskStackHorizontalViewAdapter mTaskStackViewAdapter;
83 private FinishRecentsRunnable mFinishLaunchHomeRunnable;
84
Jaewan Kimc92a7d12016-02-15 17:33:25 -080085 private PipManager mPipManager;
86 private PipManager.Listener mPipListener = new PipManager.Listener() {
87 @Override
88 public void onPipActivityClosed() {
89 mPipView.setVisibility(View.GONE);
90 mPipShadeView.setVisibility(View.GONE);
91 }
92
93 @Override
94 public void onShowPipMenu() { }
95
96 @Override
97 public void onMoveToFullscreen() { }
98
99 @Override
100 public void onPipResizeAboutToStart() { }
Jaewan Kim62338192016-02-25 10:00:05 -0800101
102 @Override
103 public void onMediaControllerChanged() { }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800104 };
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800105
106 /**
107 * A common Runnable to finish Recents by launching Home with an animation depending on the
108 * last activity launch state. Generally we always launch home when we exit Recents rather than
109 * just finishing the activity since we don't know what is behind Recents in the task stack.
110 */
111 class FinishRecentsRunnable implements Runnable {
112 Intent mLaunchIntent;
113
114 /**
115 * Creates a finish runnable that starts the specified intent.
116 */
117 public FinishRecentsRunnable(Intent launchIntent) {
118 mLaunchIntent = launchIntent;
119 }
120
121 @Override
122 public void run() {
123 try {
124 RecentsActivityLaunchState launchState =
125 Recents.getConfiguration().getLaunchState();
126 ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsTvActivity.this,
127 launchState.launchedFromSearchHome ?
128 R.anim.recents_to_search_launcher_enter :
129 R.anim.recents_to_launcher_enter,
130 launchState.launchedFromSearchHome ?
131 R.anim.recents_to_search_launcher_exit :
132 R.anim.recents_to_launcher_exit);
133 startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
134 } catch (Exception e) {
135 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
136 }
137 }
138 }
139
140 private void updateRecentsTasks() {
141 RecentsTaskLoader loader = Recents.getTaskLoader();
142 RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
143 if (plan == null) {
144 plan = loader.createLoadPlan(this);
145 }
146
147 RecentsConfiguration config = Recents.getConfiguration();
148 RecentsActivityLaunchState launchState = config.getLaunchState();
149 if (!plan.hasTasks()) {
150 loader.preloadTasks(plan, -1, launchState.launchedFromHome);
151 }
152 TaskStack stack = plan.getTaskStack();
153 RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
154 loadOpts.runningTaskId = launchState.launchedToTaskId;
155 loadOpts.numVisibleTasks = stack.getStackTaskCount();
156 loadOpts.numVisibleTaskThumbnails = stack.getStackTaskCount();
157 loader.loadTasks(this, plan, loadOpts);
158
159
160 mRecentsView.setTaskStack(stack);
Sid Soundararajan1008cc22016-02-01 11:11:14 -0800161 List stackTasks = stack.getStackTasks();
162 Collections.reverse(stackTasks);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800163 if (mTaskStackViewAdapter == null) {
Sid Soundararajan1008cc22016-02-01 11:11:14 -0800164 mTaskStackViewAdapter = new TaskStackHorizontalViewAdapter(stackTasks);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800165 mRecentsView.setTaskStackViewAdapter(mTaskStackViewAdapter);
166 } else {
Sid Soundararajan1008cc22016-02-01 11:11:14 -0800167 mTaskStackViewAdapter.setNewStackTasks(stackTasks);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800168 }
169
170 if (launchState.launchedToTaskId != -1) {
171 ArrayList<Task> tasks = stack.getStackTasks();
172 int taskCount = tasks.size();
173 for (int i = 0; i < taskCount; i++) {
174 Task t = tasks.get(i);
175 if (t.key.id == launchState.launchedToTaskId) {
176 t.isLaunchTarget = true;
177 break;
178 }
179 }
180 }
181 }
182
183 boolean dismissRecentsToLaunchTargetTaskOrHome() {
184 SystemServicesProxy ssp = Recents.getSystemServices();
185 if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
186 // If we have a focused Task, launch that Task now
187 if (mRecentsView.launchPreviousTask()) return true;
188 // If none of the other cases apply, then just go Home
189 dismissRecentsToHome(true /* animateTaskViews */);
190 }
191 return false;
192 }
193
194 boolean dismissRecentsToFocusedTaskOrHome() {
195 SystemServicesProxy ssp = Recents.getSystemServices();
196 if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
197 // If we have a focused Task, launch that Task now
198 if (mRecentsView.launchFocusedTask()) return true;
199 // If none of the other cases apply, then just go Home
200 dismissRecentsToHome(true /* animateTaskViews */);
201 return true;
202 }
203 return false;
204 }
205
206 void dismissRecentsToHome(boolean animateTaskViews) {
207 DismissRecentsToHomeAnimationStarted dismissEvent =
208 new DismissRecentsToHomeAnimationStarted(animateTaskViews);
209 dismissEvent.addPostAnimationCallback(mFinishLaunchHomeRunnable);
210 dismissEvent.addPostAnimationCallback(new Runnable() {
211 @Override
212 public void run() {
213 Recents.getSystemServices().sendCloseSystemWindows(
214 BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
215 }
216 });
217 EventBus.getDefault().send(dismissEvent);
218 }
219
220 boolean dismissRecentsToHomeIfVisible(boolean animated) {
221 SystemServicesProxy ssp = Recents.getSystemServices();
222 if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
223 // Return to Home
224 dismissRecentsToHome(animated);
225 return true;
226 }
227 return false;
228 }
229
230 @Override
231 public void onCreate(Bundle savedInstanceState) {
232 super.onCreate(savedInstanceState);
233 mFinishedOnStartup = false;
234
235 // In the case that the activity starts up before the Recents component has initialized
236 // (usually when debugging/pushing the SysUI apk), just finish this activity.
237 SystemServicesProxy ssp = Recents.getSystemServices();
238 if (ssp == null) {
239 mFinishedOnStartup = true;
240 finish();
241 return;
242 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800243 mPipManager = PipManager.getInstance();
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800244
245 // Register this activity with the event bus
246 EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
247
248 mPackageMonitor = new RecentsPackageMonitor();
249 mPackageMonitor.register(this);
250
251 // Set the Recents layout
252 setContentView(R.layout.recents_on_tv);
253
254 mRecentsView = (RecentsTvView) findViewById(R.id.recents_view);
255 mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
256 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
257 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800258 mPipView = findViewById(R.id.pip);
259 mPipShadeView = findViewById(R.id.pip_shade);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800260 getWindow().getAttributes().privateFlags |=
261 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
262
263 // Create the home intent runnable
264 Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
265 homeIntent.addCategory(Intent.CATEGORY_HOME);
266 homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
267 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
268 mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
269 }
270
271 @Override
272 protected void onNewIntent(Intent intent) {
273 super.onNewIntent(intent);
274 setIntent(intent);
275 }
276
277 @Override
278 protected void onStart() {
279 super.onStart();
280
281 // Update the recent tasks
282 updateRecentsTasks();
283
284 // If this is a new instance from a configuration change, then we have to manually trigger
285 // the enter animation state, or if recents was relaunched by AM, without going through
286 // the normal mechanisms
287 RecentsConfiguration config = Recents.getConfiguration();
288 RecentsActivityLaunchState launchState = config.getLaunchState();
289 boolean wasLaunchedByAm = !launchState.launchedFromHome &&
Winsone693aaf2016-03-01 12:05:59 -0800290 !launchState.launchedFromApp;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800291 if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
292 EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
293 }
294
295 // Notify that recents is now visible
296 SystemServicesProxy ssp = Recents.getSystemServices();
Winson88737542016-02-17 13:27:33 -0800297 EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800298
299 if (mPipManager.isPipShown()) {
300 // Place mPipView at the PIP bounds for fine tuned focus handling.
301 Rect pipBounds = mPipManager.getPipBounds();
302 LayoutParams lp = (LayoutParams) mPipView.getLayoutParams();
303 lp.width = pipBounds.width();
304 lp.height = pipBounds.height();
305 lp.leftMargin = pipBounds.left;
306 lp.topMargin = pipBounds.top;
307 mPipView.setLayoutParams(lp);
308
309 mPipView.setVisibility(View.VISIBLE);
310 mPipView.setOnClickListener(new View.OnClickListener() {
311 @Override
312 public void onClick(View v) {
313 mPipManager.resizePinnedStack(PipManager.STATE_PIP_MENU);
314 }
315 });
316 mPipView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
317 @Override
318 public void onFocusChange(View v, boolean hasFocus) {
319 mPipManager.onPipViewFocusChangedInRecents(hasFocus);
320 mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
321 }
322 });
323 mPipManager.addListener(mPipListener);
324 } else {
325 mPipView.setVisibility(View.GONE);
326 }
327 mPipManager.onRecentsStarted();
328 // Give focus to the recents row whenever its visible to an user.
329 mRecentsView.requestFocus();
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800330 }
331
332 @Override
333 public void onEnterAnimationComplete() {
334 super.onEnterAnimationComplete();
335 EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
336 }
337
338 @Override
339 protected void onStop() {
340 super.onStop();
341
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800342 mPipManager.onRecentsStopped();
343 mPipManager.removeListener(mPipListener);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800344 mIgnoreAltTabRelease = false;
345 // Notify that recents is now hidden
Winson88737542016-02-17 13:27:33 -0800346 EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800347
348 // Workaround for b/22542869, if the RecentsActivity is started again, but without going
349 // through SystemUI, we need to reset the config launch flags to ensure that we do not
350 // wait on the system to send a signal that was never queued.
351 RecentsConfiguration config = Recents.getConfiguration();
352 RecentsActivityLaunchState launchState = config.getLaunchState();
Jorim Jaggie161f082016-02-05 14:26:16 -0800353 launchState.reset();
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800354 }
355
356 @Override
357 protected void onDestroy() {
358 super.onDestroy();
359
360 // In the case that the activity finished on startup, just skip the unregistration below
361 if (mFinishedOnStartup) {
362 return;
363 }
364
365 // Unregister any broadcast receivers for the task loader
366 mPackageMonitor.unregister();
367
368 EventBus.getDefault().unregister(this);
369 }
370
371 @Override
372 public void onTrimMemory(int level) {
373 RecentsTaskLoader loader = Recents.getTaskLoader();
374 if (loader != null) {
375 loader.onTrimMemory(level);
376 }
377 }
378
379 @Override
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800380 public boolean onKeyDown(int keyCode, KeyEvent event) {
381 switch (keyCode) {
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800382 case KeyEvent.KEYCODE_DEL:
383 case KeyEvent.KEYCODE_FORWARD_DEL: {
384 EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
385 return true;
386 }
387 default:
388 break;
389 }
390 return super.onKeyDown(keyCode, event);
391 }
392
393 @Override
394 public void onUserInteraction() {
395 EventBus.getDefault().send(new UserInteractionEvent());
396 }
397
398 @Override
399 public void onBackPressed() {
400 // Back behaves like the recents button so just trigger a toggle event
401 EventBus.getDefault().send(new ToggleRecentsEvent());
402 }
403
404 /**** EventBus events ****/
405
406 public final void onBusEvent(ToggleRecentsEvent event) {
407 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
408 if (launchState.launchedFromHome) {
409 dismissRecentsToHome(true /* animateTaskViews */);
410 } else {
411 dismissRecentsToLaunchTargetTaskOrHome();
412 }
413 }
414
415 public final void onBusEvent(HideRecentsEvent event) {
416 if (event.triggeredFromAltTab) {
417 // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
418 if (!mIgnoreAltTabRelease) {
419 dismissRecentsToFocusedTaskOrHome();
420 }
421 } else if (event.triggeredFromHomeKey) {
422 dismissRecentsToHome(true /* animateTaskViews */);
423 } else {
424 // Do nothing
425 }
426 }
427
428 public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
429 EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
430 mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
431 mRecentsView.invalidate();
432 }
433
434 public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
435 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
436 int launchToTaskId = launchState.launchedToTaskId;
437 if (launchToTaskId != -1 &&
438 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
439 SystemServicesProxy ssp = Recents.getSystemServices();
440 ssp.cancelWindowTransition(launchState.launchedToTaskId);
441 ssp.cancelThumbnailTransition(getTaskId());
442 }
443 }
444
445 public final void onBusEvent(DeleteTaskDataEvent event) {
446 // Remove any stored data from the loader
447 RecentsTaskLoader loader = Recents.getTaskLoader();
448 loader.deleteTaskData(event.task, false);
449
450 // Remove the task from activity manager
451 SystemServicesProxy ssp = Recents.getSystemServices();
452 ssp.removeTask(event.task.key.id);
453 }
454
455 public final void onBusEvent(AllTaskViewsDismissedEvent event) {
456 SystemServicesProxy ssp = Recents.getSystemServices();
457 if (ssp.hasDockedTask()) {
458 mRecentsView.showEmptyView();
459 } else {
460 // Just go straight home (no animation necessary because there are no more task views)
461 dismissRecentsToHome(false /* animateTaskViews */);
462 }
463 }
464
465 public final void onBusEvent(LaunchTaskFailedEvent event) {
466 // Return to Home
467 dismissRecentsToHome(true /* animateTaskViews */);
468 }
469
470 @Override
471 public boolean onPreDraw() {
472 mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
473 // We post to make sure that this information is delivered after this traversals is
474 // finished.
475 mRecentsView.post(new Runnable() {
476 @Override
477 public void run() {
478 Recents.getSystemServices().endProlongedAnimations();
479 }
480 });
481 return true;
482 }
483}