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