RecentsActivity.java revision f1fbd77cf057e43926f9a0347692611386d09f40
1/*
2 * Copyright (C) 2014 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
17package com.android.systemui.recents;
18
19import android.app.Activity;
20import android.app.ActivityOptions;
21import android.appwidget.AppWidgetHostView;
22import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.os.Bundle;
29import android.os.UserHandle;
30import android.util.Pair;
31import android.view.KeyEvent;
32import android.view.View;
33import android.view.ViewStub;
34import com.android.systemui.R;
35import com.android.systemui.recents.model.RecentsTaskLoader;
36import com.android.systemui.recents.model.SpaceNode;
37import com.android.systemui.recents.model.TaskStack;
38import com.android.systemui.recents.views.FullscreenTransitionOverlayView;
39import com.android.systemui.recents.views.RecentsView;
40import com.android.systemui.recents.views.SystemBarScrimViews;
41import com.android.systemui.recents.views.ViewAnimation;
42
43import java.lang.reflect.InvocationTargetException;
44import java.util.ArrayList;
45
46/* Activity */
47public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
48        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
49        FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
50
51    RecentsView mRecentsView;
52    SystemBarScrimViews mScrimViews;
53    ViewStub mEmptyViewStub;
54    View mEmptyView;
55    ViewStub mFullscreenOverlayStub;
56    FullscreenTransitionOverlayView mFullScreenOverlayView;
57
58    RecentsConfiguration mConfig;
59
60    RecentsAppWidgetHost mAppWidgetHost;
61    AppWidgetProviderInfo mSearchAppWidgetInfo;
62    AppWidgetHostView mSearchAppWidgetHostView;
63
64    boolean mVisible;
65    boolean mTaskLaunched;
66
67    // Runnables to finish the Recents activity
68    FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
69    FinishRecentsRunnable mFinishWithoutAnimationRunnable = new FinishRecentsRunnable(false);
70    FinishRecentsRunnable mFinishLaunchHomeRunnable;
71
72    /**
73     * A Runnable to finish Recents either with/without a transition, and either by calling finish()
74     * or just launching the specified intent.
75     */
76    class FinishRecentsRunnable implements Runnable {
77        boolean mUseCustomFinishTransition;
78        Intent mLaunchIntent;
79        ActivityOptions mLaunchOpts;
80
81        public FinishRecentsRunnable(boolean withTransition) {
82            mUseCustomFinishTransition = withTransition;
83        }
84
85        public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
86            mLaunchIntent = launchIntent;
87            mLaunchOpts = opts;
88        }
89
90        @Override
91        public void run() {
92            // Mark Recents as no longer visible
93            AlternateRecentsComponent.notifyVisibilityChanged(false);
94            // Finish Recents
95            if (mLaunchIntent != null) {
96                if (mLaunchOpts != null) {
97                    startActivityAsUser(mLaunchIntent, new UserHandle(UserHandle.USER_CURRENT));
98                } else {
99                    startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(),
100                            new UserHandle(UserHandle.USER_CURRENT));
101                }
102            } else {
103                finish();
104                if (mUseCustomFinishTransition) {
105                    overridePendingTransition(R.anim.recents_to_launcher_enter,
106                            R.anim.recents_to_launcher_exit);
107                }
108            }
109        }
110    }
111
112    // Broadcast receiver to handle messages from our RecentsService
113    BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
114        @Override
115        public void onReceive(Context context, Intent intent) {
116            String action = intent.getAction();
117            if (Console.Enabled) {
118                Console.log(Constants.Log.App.SystemUIHandshake,
119                        "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
120            }
121            if (action.equals(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY)) {
122                if (intent.getBooleanExtra(RecentsService.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
123                    // Dismiss recents, launching the focused task
124                    dismissRecentsIfVisible();
125                } else {
126                    // If we are mid-animation into Recents, then reverse it and finish
127                    if (mFullScreenOverlayView == null ||
128                            !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
129                        // Otherwise, either finish Recents, or launch Home directly
130                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
131                                null, mFinishLaunchHomeRunnable, null);
132                        mRecentsView.startExitToHomeAnimation(
133                                new ViewAnimation.TaskViewExitContext(exitTrigger));
134                    }
135                }
136            } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
137                // Try and unfilter and filtered stacks
138                if (!mRecentsView.unfilterFilteredStacks()) {
139                    // If there are no filtered stacks, dismiss recents and launch the first task
140                    dismissRecentsIfVisible();
141                }
142            } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
143                // Try and start the enter animation (or restart it on configuration changed)
144                mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
145                // Call our callback
146                onEnterAnimationTriggered();
147            }
148        }
149    };
150
151    // Broadcast receiver to handle messages from the system
152    BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
153        @Override
154        public void onReceive(Context context, Intent intent) {
155            mFinishWithoutAnimationRunnable.run();
156        }
157    };
158
159    /** Updates the set of recent tasks */
160    void updateRecentsTasks(Intent launchIntent) {
161        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
162        SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
163        ArrayList<TaskStack> stacks = root.getStacks();
164        if (!stacks.isEmpty()) {
165            mRecentsView.setBSP(root);
166        }
167
168        // Update the configuration based on the launch intent
169        mConfig.launchedFromHome = launchIntent.getBooleanExtra(
170                AlternateRecentsComponent.EXTRA_FROM_HOME, false);
171        mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
172                AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false);
173        mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra(
174                AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false);
175        mConfig.launchedWithAltTab = launchIntent.getBooleanExtra(
176                AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
177        mConfig.launchedWithNoRecentTasks = !root.hasTasks();
178
179        // Show the scrim if we animate into Recents without window transitions
180        mScrimViews.prepareEnterRecentsAnimation();
181
182        // Add the default no-recents layout
183        if (mEmptyView == null) {
184            mEmptyView = mEmptyViewStub.inflate();
185        }
186        if (mConfig.launchedWithNoRecentTasks) {
187            mEmptyView.setVisibility(View.VISIBLE);
188        } else {
189            mEmptyView.setVisibility(View.GONE);
190        }
191    }
192
193    /** Attempts to allocate and bind the search bar app widget */
194    void bindSearchBarAppWidget() {
195        if (Constants.DebugFlags.App.EnableSearchLayout) {
196            SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
197
198            // Reset the host view and widget info
199            mSearchAppWidgetHostView = null;
200            mSearchAppWidgetInfo = null;
201
202            // Try and load the app widget id from the settings
203            int appWidgetId = mConfig.searchBarAppWidgetId;
204            if (appWidgetId >= 0) {
205                mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
206                if (mSearchAppWidgetInfo == null) {
207                    // If there is no actual widget associated with that id, then delete it and
208                    // prepare to bind another app widget in its place
209                    ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
210                    appWidgetId = -1;
211                }
212                if (Console.Enabled) {
213                    Console.log(Constants.Log.App.SystemUIHandshake,
214                            "[RecentsActivity|onCreate|settings|appWidgetId]",
215                            "Id: " + appWidgetId,
216                            Console.AnsiBlue);
217                }
218            }
219
220            // If there is no id, then bind a new search app widget
221            if (appWidgetId < 0) {
222                Pair<Integer, AppWidgetProviderInfo> widgetInfo =
223                        ssp.bindSearchAppWidget(mAppWidgetHost);
224                if (widgetInfo != null) {
225                    if (Console.Enabled) {
226                        Console.log(Constants.Log.App.SystemUIHandshake,
227                                "[RecentsActivity|onCreate|searchWidget]",
228                                "Id: " + widgetInfo.first + " Info: " + widgetInfo.second,
229                                Console.AnsiBlue);
230                    }
231
232                    // Save the app widget id into the settings
233                    mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
234                    mSearchAppWidgetInfo = widgetInfo.second;
235                }
236            }
237        }
238    }
239
240    /** Creates the search bar app widget view */
241    void addSearchBarAppWidgetView() {
242        if (Constants.DebugFlags.App.EnableSearchLayout) {
243            int appWidgetId = mConfig.searchBarAppWidgetId;
244            if (appWidgetId >= 0) {
245                if (Console.Enabled) {
246                    Console.log(Constants.Log.App.SystemUIHandshake,
247                            "[RecentsActivity|onCreate|addSearchAppWidgetView]",
248                            "Id: " + appWidgetId,
249                            Console.AnsiBlue);
250                }
251                mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
252                        mSearchAppWidgetInfo);
253                Bundle opts = new Bundle();
254                opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
255                        AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
256                mSearchAppWidgetHostView.updateAppWidgetOptions(opts);
257                // Set the padding to 0 for this search widget
258                mSearchAppWidgetHostView.setPadding(0, 0, 0, 0);
259                mRecentsView.setSearchBar(mSearchAppWidgetHostView);
260            } else {
261                mRecentsView.setSearchBar(null);
262            }
263        }
264    }
265
266    /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
267    boolean dismissRecentsIfVisible() {
268        if (mVisible) {
269            // If we are mid-animation into Recents, then reverse it and finish
270            if (mFullScreenOverlayView == null ||
271                    !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
272                // If we have a focused task, then launch that task
273                if (!mRecentsView.launchFocusedTask()) {
274                    if (mConfig.launchedFromHome) {
275                        // Just start the animation out of recents
276                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
277                                null, mFinishLaunchHomeRunnable, null);
278                        mRecentsView.startExitToHomeAnimation(
279                                new ViewAnimation.TaskViewExitContext(exitTrigger));
280                    } else {
281                        // Otherwise, try and launch the first task
282                        if (!mRecentsView.launchFirstTask()) {
283                            // If there are no tasks, then just finish recents
284                            mFinishLaunchHomeRunnable.run();
285                        }
286                    }
287                }
288            }
289            return true;
290        }
291        return false;
292    }
293
294    /** Called with the activity is first created. */
295    @Override
296    public void onCreate(Bundle savedInstanceState) {
297        super.onCreate(savedInstanceState);
298        if (Console.Enabled) {
299            Console.logDivider(Constants.Log.App.SystemUIHandshake);
300            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
301                    getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
302            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
303                    Constants.Log.App.TimeRecentsStartupKey, "onCreate");
304        }
305
306        // Initialize the loader and the configuration
307        RecentsTaskLoader.initialize(this);
308        mConfig = RecentsConfiguration.reinitialize(this);
309
310        // Create the home intent runnable
311        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
312        homeIntent.addCategory(Intent.CATEGORY_HOME);
313        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
314                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
315        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
316                ActivityOptions.makeCustomAnimation(this, R.anim.recents_to_launcher_enter,
317                        R.anim.recents_to_launcher_exit));
318
319        // Initialize the widget host (the host id is static and does not change)
320        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
321
322        // Set the Recents layout
323        setContentView(R.layout.recents);
324        mRecentsView = (RecentsView) findViewById(R.id.recents_view);
325        mRecentsView.setCallbacks(this);
326        mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
327                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
328                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
329        mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
330        mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
331        mScrimViews = new SystemBarScrimViews(this, mConfig);
332
333        // Update the recent tasks
334        updateRecentsTasks(getIntent());
335
336        // Prepare the screenshot transition if necessary
337        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
338            mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
339            mFullScreenOverlayView.setCallbacks(this);
340            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
341        }
342
343        // Bind the search app widget when we first start up
344        bindSearchBarAppWidget();
345        // Add the search bar layout
346        addSearchBarAppWidgetView();
347
348        // Update if we are getting a configuration change
349        if (savedInstanceState != null) {
350            onConfigurationChange();
351        }
352
353        // Private API calls to make the shadows look better
354        try {
355            Utilities.setShadowProperty("ambientShadowStrength", String.valueOf(35f));
356            Utilities.setShadowProperty("ambientRatio", String.valueOf(0.5f));
357        } catch (IllegalAccessException e) {
358            e.printStackTrace();
359        } catch (InvocationTargetException e) {
360            e.printStackTrace();
361        }
362    }
363
364    void onConfigurationChange() {
365        // Try and start the enter animation (or restart it on configuration changed)
366        mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
367        // Call our callback
368        onEnterAnimationTriggered();
369    }
370
371    @Override
372    protected void onNewIntent(Intent intent) {
373        super.onNewIntent(intent);
374        // Reset the task launched flag if we encounter an onNewIntent() before onStop()
375        mTaskLaunched = false;
376
377        if (Console.Enabled) {
378            Console.logDivider(Constants.Log.App.SystemUIHandshake);
379            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
380                    intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
381            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
382                    Constants.Log.App.TimeRecentsStartupKey, "onNewIntent");
383        }
384
385        // Initialize the loader and the configuration
386        RecentsTaskLoader.initialize(this);
387        mConfig = RecentsConfiguration.reinitialize(this);
388
389        // Update the recent tasks
390        updateRecentsTasks(intent);
391
392        // Prepare the screenshot transition if necessary
393        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
394            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
395        }
396
397        // Don't attempt to rebind the search bar widget, but just add the search bar layout
398        addSearchBarAppWidgetView();
399    }
400
401    @Override
402    protected void onStart() {
403        if (Console.Enabled) {
404            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
405                    Console.AnsiRed);
406        }
407        super.onStart();
408
409        mVisible = true;
410    }
411
412    @Override
413    protected void onResume() {
414        if (Console.Enabled) {
415            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
416                    Console.AnsiRed);
417        }
418        super.onResume();
419    }
420
421    @Override
422    public void onAttachedToWindow() {
423        if (Console.Enabled) {
424            Console.log(Constants.Log.App.SystemUIHandshake,
425                    "[RecentsActivity|onAttachedToWindow]", "",
426                    Console.AnsiRed);
427        }
428        super.onAttachedToWindow();
429
430        // Register the broadcast receiver to handle messages from our service
431        IntentFilter filter = new IntentFilter();
432        filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
433        filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
434        filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
435        registerReceiver(mServiceBroadcastReceiver, filter);
436
437        // Register the broadcast receiver to handle messages when the screen is turned off
438        filter = new IntentFilter();
439        filter.addAction(Intent.ACTION_SCREEN_OFF);
440        registerReceiver(mScreenOffReceiver, filter);
441
442        // Register any broadcast receivers for the task loader
443        RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView);
444
445        // Start listening for widget package changes if there is one bound
446        if (mConfig.searchBarAppWidgetId >= 0) {
447            mAppWidgetHost.startListening(this);
448        }
449    }
450
451    @Override
452    public void onDetachedFromWindow() {
453        if (Console.Enabled) {
454            Console.log(Constants.Log.App.SystemUIHandshake,
455                    "[RecentsActivity|onDetachedFromWindow]", "",
456                    Console.AnsiRed);
457        }
458        super.onDetachedFromWindow();
459
460        // Unregister any broadcast receivers we have registered
461        unregisterReceiver(mServiceBroadcastReceiver);
462        unregisterReceiver(mScreenOffReceiver);
463        RecentsTaskLoader.getInstance().unregisterReceivers();
464
465        // Stop listening for widget package changes if there was one bound
466        if (mConfig.searchBarAppWidgetId >= 0) {
467            mAppWidgetHost.stopListening();
468        }
469    }
470
471    @Override
472    protected void onPause() {
473        if (Console.Enabled) {
474            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
475                    Console.AnsiRed);
476        }
477        super.onPause();
478    }
479
480    @Override
481    protected void onStop() {
482        if (Console.Enabled) {
483            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
484                    Console.AnsiRed);
485        }
486        super.onStop();
487
488        mVisible = false;
489        mTaskLaunched = false;
490    }
491
492    @Override
493    protected void onDestroy() {
494        if (Console.Enabled) {
495            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "",
496                    Console.AnsiRed);
497        }
498        super.onDestroy();
499    }
500
501    @Override
502    public void onTrimMemory(int level) {
503        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
504        if (loader != null) {
505            loader.onTrimMemory(level);
506        }
507    }
508
509    @Override
510    public boolean onKeyDown(int keyCode, KeyEvent event) {
511        if (keyCode == KeyEvent.KEYCODE_TAB) {
512            // Focus the next task in the stack
513            final boolean backward = event.isShiftPressed();
514            mRecentsView.focusNextTask(!backward);
515            return true;
516        }
517
518        return super.onKeyDown(keyCode, event);
519    }
520
521    @Override
522    public void onUserInteraction() {
523        mRecentsView.onUserInteraction();
524    }
525
526    @Override
527    public void onBackPressed() {
528        // If we are mid-animation into Recents, then reverse it and finish
529        if (mFullScreenOverlayView == null ||
530                !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
531            // If we are currently filtering in any stacks, unfilter them first
532            if (!mRecentsView.unfilterFilteredStacks()) {
533                if (mConfig.launchedFromHome) {
534                    // Just start the animation out of recents
535                    ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
536                            null, mFinishLaunchHomeRunnable, null);
537                    mRecentsView.startExitToHomeAnimation(
538                            new ViewAnimation.TaskViewExitContext(exitTrigger));
539                } else {
540                    // Otherwise, try and launch the first task
541                    if (!mRecentsView.launchFirstTask()) {
542                        // If there are no tasks, then just finish recents
543                        mFinishLaunchHomeRunnable.run();
544                    }
545                }
546            }
547        }
548    }
549
550    public void onEnterAnimationTriggered() {
551        // Animate the scrims in
552        mScrimViews.startEnterRecentsAnimation();
553    }
554
555    /**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/
556
557    @Override
558    public void onEnterAnimationComplete(boolean canceled) {
559        if (!canceled) {
560            // Reset the full screenshot transition view
561            if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
562                mFullScreenOverlayView.reset();
563            }
564
565            // XXX: We should clean up the screenshot in this case as well, but it needs to happen
566            //      after to animate up
567        }
568        // Recycle the full screen screenshot
569        AlternateRecentsComponent.consumeLastScreenshot();
570    }
571
572    /**** RecentsView.RecentsViewCallbacks Implementation ****/
573
574    @Override
575    public void onExitToHomeAnimationTriggered() {
576        // Animate the scrims out
577        mScrimViews.startExitRecentsAnimation();
578    }
579
580    @Override
581    public void onTaskLaunching() {
582        mTaskLaunched = true;
583
584        // Mark recents as no longer visible
585        AlternateRecentsComponent.notifyVisibilityChanged(false);
586    }
587
588    /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
589
590    @Override
591    public void refreshSearchWidget() {
592        // Load the Search widget again
593        bindSearchBarAppWidget();
594        addSearchBarAppWidgetView();
595    }
596}
597