RecentsActivity.java revision aef51c63d7086088b0c245d18f052a181fe1ff45
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.appwidget.AppWidgetHost;
21import android.appwidget.AppWidgetHostView;
22import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.res.Configuration;
30import android.os.Bundle;
31import android.util.Pair;
32import android.view.Gravity;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.WindowManager;
38import android.widget.FrameLayout;
39import com.android.systemui.R;
40import com.android.systemui.recents.model.SpaceNode;
41import com.android.systemui.recents.model.TaskStack;
42import com.android.systemui.recents.views.RecentsView;
43
44import java.util.ArrayList;
45import java.util.Set;
46
47/** Our special app widget host */
48class RecentsAppWidgetHost extends AppWidgetHost {
49    /* Callbacks to notify when an app package changes */
50    interface RecentsAppWidgetHostCallbacks {
51        public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo);
52    }
53
54    RecentsAppWidgetHostCallbacks mCb;
55
56    public RecentsAppWidgetHost(Context context, int hostId, RecentsAppWidgetHostCallbacks cb) {
57        super(context, hostId);
58        mCb = cb;
59    }
60
61    @Override
62    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
63        mCb.onProviderChanged(appWidgetId, appWidget);
64    }
65}
66
67/* Activity */
68public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
69        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
70    FrameLayout mContainerView;
71    RecentsView mRecentsView;
72    View mEmptyView;
73    View mNavBarScrimView;
74
75    AppWidgetHost mAppWidgetHost;
76    AppWidgetProviderInfo mSearchAppWidgetInfo;
77    AppWidgetHostView mSearchAppWidgetHostView;
78
79    boolean mVisible;
80    boolean mTaskLaunched;
81
82    // Broadcast receiver to handle messages from our RecentsService
83    BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
84        @Override
85        public void onReceive(Context context, Intent intent) {
86            String action = intent.getAction();
87            if (Console.Enabled) {
88                Console.log(Constants.Log.App.SystemUIHandshake,
89                        "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
90            }
91            if (action.equals(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY)) {
92                if (intent.getBooleanExtra(RecentsService.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
93                    // Dismiss recents, launching the focused task
94                    dismissRecentsIfVisible();
95                } else {
96                    // Otherwise, just finish the activity without launching any other activities
97                    finish();
98                }
99            } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
100                // Try and unfilter and filtered stacks
101                if (!mRecentsView.unfilterFilteredStacks()) {
102                    // If there are no filtered stacks, dismiss recents and launch the first task
103                    dismissRecentsIfVisible();
104                }
105            } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
106                // Try and start the enter animation (or restart it on configuration changed)
107                mRecentsView.startOnEnterAnimation();
108            }
109        }
110    };
111
112    // Broadcast receiver to handle messages from the system
113    BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
114        @Override
115        public void onReceive(Context context, Intent intent) {
116            finish();
117        }
118    };
119
120    /** Updates the set of recent tasks */
121    void updateRecentsTasks(Intent launchIntent) {
122        // Update the configuration based on the launch intent
123        RecentsConfiguration config = RecentsConfiguration.getInstance();
124        config.launchedWithThumbnailAnimation = launchIntent.getBooleanExtra(
125                AlternateRecentsComponent.EXTRA_ANIMATING_WITH_THUMBNAIL, false);
126        config.launchedFromAltTab = launchIntent.getBooleanExtra(
127                AlternateRecentsComponent.EXTRA_FROM_ALT_TAB, false);
128
129        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
130        SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
131        ArrayList<TaskStack> stacks = root.getStacks();
132        if (!stacks.isEmpty()) {
133            mRecentsView.setBSP(root);
134        }
135
136        // Hide the scrim by default when we enter recents
137        mNavBarScrimView.setVisibility(View.INVISIBLE);
138
139        // Add the default no-recents layout
140        if (stacks.size() == 1 && stacks.get(0).getTaskCount() == 0) {
141            mEmptyView.setVisibility(View.VISIBLE);
142
143            // Dim the background even more
144            WindowManager.LayoutParams wlp = getWindow().getAttributes();
145            wlp.dimAmount = Constants.Values.Window.DarkBackgroundDim;
146            getWindow().setAttributes(wlp);
147            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
148        } else {
149            mEmptyView.setVisibility(View.GONE);
150
151            // Un-dim the background
152            WindowManager.LayoutParams wlp = getWindow().getAttributes();
153            wlp.dimAmount = 0f;
154            getWindow().setAttributes(wlp);
155            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
156        }
157    }
158
159    /** Attempts to allocate and bind the search bar app widget */
160    void bindSearchBarAppWidget() {
161        if (Constants.DebugFlags.App.EnableSearchLayout) {
162            RecentsConfiguration config = RecentsConfiguration.getInstance();
163            SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
164
165            // Reset the host view and widget info
166            mSearchAppWidgetHostView = null;
167            mSearchAppWidgetInfo = null;
168
169            // Try and load the app widget id from the settings
170            int appWidgetId = config.searchBarAppWidgetId;
171            if (appWidgetId >= 0) {
172                mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
173                if (mSearchAppWidgetInfo == null) {
174                    // If there is no actual widget associated with that id, then delete it and
175                    // prepare to bind another app widget in its place
176                    ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
177                    appWidgetId = -1;
178                }
179                if (Console.Enabled) {
180                    Console.log(Constants.Log.App.SystemUIHandshake,
181                            "[RecentsActivity|onCreate|settings|appWidgetId]",
182                            "Id: " + appWidgetId,
183                            Console.AnsiBlue);
184                }
185            }
186
187            // If there is no id, then bind a new search app widget
188            if (appWidgetId < 0) {
189                Pair<Integer, AppWidgetProviderInfo> widgetInfo =
190                        ssp.bindSearchAppWidget(mAppWidgetHost);
191                if (widgetInfo != null) {
192                    if (Console.Enabled) {
193                        Console.log(Constants.Log.App.SystemUIHandshake,
194                                "[RecentsActivity|onCreate|searchWidget]",
195                                "Id: " + widgetInfo.first + " Info: " + widgetInfo.second,
196                                Console.AnsiBlue);
197                    }
198
199                    // Save the app widget id into the settings
200                    config.updateSearchBarAppWidgetId(this, widgetInfo.first);
201                    mSearchAppWidgetInfo = widgetInfo.second;
202                }
203            }
204        }
205    }
206
207    /** Creates the search bar app widget view */
208    void addSearchBarAppWidgetView() {
209        if (Constants.DebugFlags.App.EnableSearchLayout) {
210            RecentsConfiguration config = RecentsConfiguration.getInstance();
211            int appWidgetId = config.searchBarAppWidgetId;
212            if (appWidgetId >= 0) {
213                if (Console.Enabled) {
214                    Console.log(Constants.Log.App.SystemUIHandshake,
215                            "[RecentsActivity|onCreate|addSearchAppWidgetView]",
216                            "Id: " + appWidgetId,
217                            Console.AnsiBlue);
218                }
219                mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
220                        mSearchAppWidgetInfo);
221                Bundle opts = new Bundle();
222                opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
223                        AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
224                mSearchAppWidgetHostView.updateAppWidgetOptions(opts);
225                // Set the padding to 0 for this search widget
226                mSearchAppWidgetHostView.setPadding(0, 0, 0, 0);
227                mRecentsView.setSearchBar(mSearchAppWidgetHostView);
228            } else {
229                mRecentsView.setSearchBar(null);
230            }
231        }
232    }
233
234    /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
235    boolean dismissRecentsIfVisible() {
236        if (mVisible) {
237            if (!mRecentsView.launchFocusedTask()) {
238                if (!mRecentsView.launchFirstTask()) {
239                    finish();
240                }
241            }
242            return true;
243        }
244        return false;
245    }
246
247    /** Called with the activity is first created. */
248    @Override
249    public void onCreate(Bundle savedInstanceState) {
250        super.onCreate(savedInstanceState);
251        if (Console.Enabled) {
252            Console.logDivider(Constants.Log.App.SystemUIHandshake);
253            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
254                    getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
255            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
256                    Constants.Log.App.TimeRecentsStartupKey, "onCreate");
257        }
258
259        // Initialize the loader and the configuration
260        RecentsTaskLoader.initialize(this);
261        RecentsConfiguration.reinitialize(this);
262
263        // Initialize the widget host (the host id is static and does not change)
264        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this);
265
266        // Create the view hierarchy
267        mRecentsView = new RecentsView(this);
268        mRecentsView.setCallbacks(this);
269        mRecentsView.setLayoutParams(new FrameLayout.LayoutParams(
270                FrameLayout.LayoutParams.MATCH_PARENT,
271                FrameLayout.LayoutParams.MATCH_PARENT));
272        mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
273                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
274                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
275
276        // Create the empty view
277        LayoutInflater inflater = LayoutInflater.from(this);
278        mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false);
279        mNavBarScrimView = inflater.inflate(R.layout.recents_nav_bar_scrim, mContainerView, false);
280
281        mContainerView = new FrameLayout(this);
282        mContainerView.addView(mRecentsView);
283        mContainerView.addView(mEmptyView);
284        mContainerView.addView(mNavBarScrimView);
285        setContentView(mContainerView);
286
287        // Update the recent tasks
288        updateRecentsTasks(getIntent());
289
290        // Bind the search app widget when we first start up
291        bindSearchBarAppWidget();
292        // Add the search bar layout
293        addSearchBarAppWidgetView();
294
295        // Update if we are getting a configuration change
296        if (savedInstanceState != null) {
297            onConfigurationChange();
298        }
299    }
300
301    void onConfigurationChange() {
302        // Try and start the enter animation (or restart it on configuration changed)
303        mRecentsView.startOnEnterAnimation();
304    }
305
306    @Override
307    protected void onNewIntent(Intent intent) {
308        super.onNewIntent(intent);
309        // Reset the task launched flag if we encounter an onNewIntent() before onStop()
310        mTaskLaunched = false;
311
312        if (Console.Enabled) {
313            Console.logDivider(Constants.Log.App.SystemUIHandshake);
314            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
315                    intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
316            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
317                    Constants.Log.App.TimeRecentsStartupKey, "onNewIntent");
318        }
319
320        // Initialize the loader and the configuration
321        RecentsTaskLoader.initialize(this);
322        RecentsConfiguration.reinitialize(this);
323
324        // Update the recent tasks
325        updateRecentsTasks(intent);
326
327        // Don't attempt to rebind the search bar widget, but just add the search bar layout
328        addSearchBarAppWidgetView();
329    }
330
331    @Override
332    protected void onStart() {
333        if (Console.Enabled) {
334            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
335                    Console.AnsiRed);
336        }
337        super.onStart();
338
339        // Start listening for widget package changes if there is one bound
340        RecentsConfiguration config = RecentsConfiguration.getInstance();
341        if (config.searchBarAppWidgetId >= 0) {
342            mAppWidgetHost.startListening();
343        }
344
345        mVisible = true;
346    }
347
348    @Override
349    protected void onResume() {
350        if (Console.Enabled) {
351            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
352                    Console.AnsiRed);
353        }
354        super.onResume();
355    }
356
357    @Override
358    public void onAttachedToWindow() {
359        if (Console.Enabled) {
360            Console.log(Constants.Log.App.SystemUIHandshake,
361                    "[RecentsActivity|onAttachedToWindow]", "",
362                    Console.AnsiRed);
363        }
364        super.onAttachedToWindow();
365
366        // Register the broadcast receiver to handle messages from our service
367        IntentFilter filter = new IntentFilter();
368        filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
369        filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
370        filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
371        registerReceiver(mServiceBroadcastReceiver, filter);
372
373        // Register the broadcast receiver to handle messages when the screen is turned off
374        filter = new IntentFilter();
375        filter.addAction(Intent.ACTION_SCREEN_OFF);
376        registerReceiver(mScreenOffReceiver, filter);
377
378        // Register any broadcast receivers for the task loader
379        RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView);
380    }
381
382    @Override
383    public void onDetachedFromWindow() {
384        if (Console.Enabled) {
385            Console.log(Constants.Log.App.SystemUIHandshake,
386                    "[RecentsActivity|onDetachedFromWindow]", "",
387                    Console.AnsiRed);
388        }
389        super.onDetachedFromWindow();
390
391        // Unregister any broadcast receivers we have registered
392        unregisterReceiver(mServiceBroadcastReceiver);
393        unregisterReceiver(mScreenOffReceiver);
394        RecentsTaskLoader.getInstance().unregisterReceivers();
395    }
396
397    @Override
398    protected void onPause() {
399        if (Console.Enabled) {
400            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
401                    Console.AnsiRed);
402        }
403        super.onPause();
404    }
405
406    @Override
407    protected void onStop() {
408        if (Console.Enabled) {
409            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
410                    Console.AnsiRed);
411        }
412        super.onStop();
413
414        // Stop listening for widget package changes if there was one bound
415        RecentsConfiguration config = RecentsConfiguration.getInstance();
416        if (config.searchBarAppWidgetId >= 0) {
417            mAppWidgetHost.stopListening();
418        }
419
420        mVisible = false;
421        mTaskLaunched = false;
422    }
423
424    @Override
425    protected void onDestroy() {
426        if (Console.Enabled) {
427            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "",
428                    Console.AnsiRed);
429        }
430        super.onDestroy();
431    }
432
433    @Override
434    public void onTrimMemory(int level) {
435        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
436        if (loader != null) {
437            loader.onTrimMemory(level);
438        }
439    }
440
441    @Override
442    public boolean onKeyDown(int keyCode, KeyEvent event) {
443        if (keyCode == KeyEvent.KEYCODE_TAB) {
444            // Focus the next task in the stack
445            final boolean backward = event.isShiftPressed();
446            mRecentsView.focusNextTask(!backward);
447            return true;
448        }
449
450        return super.onKeyDown(keyCode, event);
451    }
452
453    @Override
454    public void onBackPressed() {
455        // Unfilter any stacks
456        if (!mRecentsView.unfilterFilteredStacks()) {
457            if (!mRecentsView.launchFirstTask()) {
458                super.onBackPressed();
459            }
460        }
461    }
462
463    @Override
464    public void onEnterAnimationTriggered() {
465        // Fade in the scrim
466        RecentsConfiguration config = RecentsConfiguration.getInstance();
467        if (config.hasNavBarScrim()) {
468            mNavBarScrimView.setVisibility(View.VISIBLE);
469            mNavBarScrimView.setAlpha(0f);
470            mNavBarScrimView.animate().alpha(1f)
471                    .setStartDelay(config.taskBarEnterAnimDelay)
472                    .setDuration(config.navBarScrimEnterDuration)
473                    .setInterpolator(config.fastOutSlowInInterpolator)
474                    .withLayer()
475                    .start();
476        }
477    }
478
479    @Override
480    public void onTaskLaunching(boolean isTaskInStackBounds) {
481        mTaskLaunched = true;
482
483        // Fade out the scrim
484        RecentsConfiguration config = RecentsConfiguration.getInstance();
485        if (!isTaskInStackBounds && config.hasNavBarScrim()) {
486            mNavBarScrimView.animate().alpha(0f)
487                    .setStartDelay(0)
488                    .setDuration(config.taskBarExitAnimDuration)
489                    .setInterpolator(config.fastOutSlowInInterpolator)
490                    .withLayer()
491                    .start();
492        }
493    }
494
495    @Override
496    public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
497        RecentsConfiguration config = RecentsConfiguration.getInstance();
498        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
499        if (appWidgetId > -1 && appWidgetId == config.searchBarAppWidgetId) {
500            // The search provider may have changed, so just delete the old widget and bind it again
501            ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
502            config.updateSearchBarAppWidgetId(this, -1);
503            // Load the widget again
504            bindSearchBarAppWidget();
505            addSearchBarAppWidgetView();
506        }
507    }
508}
509