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