OSDN Git Service

9d2d6eeabc72f7d36ffad391237629273c972024
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / recent / RecentTasksLoader.java
1 /*
2  * Copyright (C) 2011 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
17 package com.android.systemui.recent;
18
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.res.Resources;
27 import android.graphics.Bitmap;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.os.AsyncTask;
31 import android.os.Handler;
32 import android.os.Process;
33 import android.os.UserHandle;
34 import android.util.Log;
35 import android.view.MotionEvent;
36 import android.view.View;
37
38 import com.android.systemui.R;
39 import com.android.systemui.statusbar.phone.PhoneStatusBar;
40
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.BlockingQueue;
44 import java.util.concurrent.LinkedBlockingQueue;
45
46 public class RecentTasksLoader implements View.OnTouchListener {
47     static final String TAG = "RecentTasksLoader";
48     static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
49
50     private static final int DISPLAY_TASKS = 20;
51     private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
52
53     private Context mContext;
54     private RecentsPanelView mRecentsPanel;
55
56     private Object mFirstTaskLock = new Object();
57     private TaskDescription mFirstTask;
58     private boolean mFirstTaskLoaded;
59
60     private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader;
61     private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader;
62     private Handler mHandler;
63
64     private int mIconDpi;
65     private ColorDrawableWithDimensions mDefaultThumbnailBackground;
66     private ColorDrawableWithDimensions mDefaultIconBackground;
67     private int mNumTasksInFirstScreenful = Integer.MAX_VALUE;
68
69     private boolean mFirstScreenful;
70     private ArrayList<TaskDescription> mLoadedTasks;
71
72     private enum State { LOADING, LOADED, CANCELLED };
73     private State mState = State.CANCELLED;
74
75
76     private static RecentTasksLoader sInstance;
77     public static RecentTasksLoader getInstance(Context context) {
78         if (sInstance == null) {
79             sInstance = new RecentTasksLoader(context);
80         }
81         return sInstance;
82     }
83
84     private RecentTasksLoader(Context context) {
85         mContext = context;
86         mHandler = new Handler();
87
88         final Resources res = context.getResources();
89
90         // get the icon size we want -- on tablets, we use bigger icons
91         boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
92         if (isTablet) {
93             ActivityManager activityManager =
94                     (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
95             mIconDpi = activityManager.getLauncherLargeIconDensity();
96         } else {
97             mIconDpi = res.getDisplayMetrics().densityDpi;
98         }
99
100         // Render default icon (just a blank image)
101         int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size);
102         int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi);
103         mDefaultIconBackground = new ColorDrawableWithDimensions(0x00000000, iconSize, iconSize);
104
105         // Render the default thumbnail background
106         int thumbnailWidth =
107                 (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
108         int thumbnailHeight =
109                 (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
110         int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
111
112         mDefaultThumbnailBackground =
113                 new ColorDrawableWithDimensions(color, thumbnailWidth, thumbnailHeight);
114     }
115
116     public void setRecentsPanel(RecentsPanelView newRecentsPanel, RecentsPanelView caller) {
117         // Only allow clearing mRecentsPanel if the caller is the current recentsPanel
118         if (newRecentsPanel != null || mRecentsPanel == caller) {
119             mRecentsPanel = newRecentsPanel;
120             if (mRecentsPanel != null) {
121                 mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful();
122             }
123         }
124     }
125
126     public Drawable getDefaultThumbnail() {
127         return mDefaultThumbnailBackground;
128     }
129
130     public Drawable getDefaultIcon() {
131         return mDefaultIconBackground;
132     }
133
134     public ArrayList<TaskDescription> getLoadedTasks() {
135         return mLoadedTasks;
136     }
137
138     public void remove(TaskDescription td) {
139         mLoadedTasks.remove(td);
140     }
141
142     public boolean isFirstScreenful() {
143         return mFirstScreenful;
144     }
145
146     private boolean isCurrentHomeActivity(ComponentName component, ActivityInfo homeInfo) {
147         if (homeInfo == null) {
148             final PackageManager pm = mContext.getPackageManager();
149             homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
150                 .resolveActivityInfo(pm, 0);
151         }
152         return homeInfo != null
153             && homeInfo.packageName.equals(component.getPackageName())
154             && homeInfo.name.equals(component.getClassName());
155     }
156
157     // Create an TaskDescription, returning null if the title or icon is null
158     TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
159             ComponentName origActivity, CharSequence description) {
160         Intent intent = new Intent(baseIntent);
161         if (origActivity != null) {
162             intent.setComponent(origActivity);
163         }
164         final PackageManager pm = mContext.getPackageManager();
165         intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
166                 | Intent.FLAG_ACTIVITY_NEW_TASK);
167         final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
168         if (resolveInfo != null) {
169             final ActivityInfo info = resolveInfo.activityInfo;
170             final String title = info.loadLabel(pm).toString();
171
172             if (title != null && title.length() > 0) {
173                 if (DEBUG) Log.v(TAG, "creating activity desc for id="
174                         + persistentTaskId + ", label=" + title);
175
176                 TaskDescription item = new TaskDescription(taskId,
177                         persistentTaskId, resolveInfo, baseIntent, info.packageName,
178                         description);
179                 item.setLabel(title);
180
181                 return item;
182             } else {
183                 if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
184             }
185         }
186         return null;
187     }
188
189     void loadThumbnailAndIcon(TaskDescription td) {
190         final ActivityManager am = (ActivityManager)
191                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
192         final PackageManager pm = mContext.getPackageManager();
193         Bitmap thumbnail = am.getTaskTopThumbnail(td.persistentTaskId);
194         Drawable icon = getFullResIcon(td.resolveInfo, pm);
195
196         if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
197                 + td + ": " + thumbnail);
198         synchronized (td) {
199             if (thumbnail != null) {
200                 td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail));
201             } else {
202                 td.setThumbnail(mDefaultThumbnailBackground);
203             }
204             if (icon != null) {
205                 td.setIcon(icon);
206             }
207             td.setLoaded(true);
208         }
209     }
210
211     Drawable getFullResDefaultActivityIcon() {
212         return getFullResIcon(Resources.getSystem(),
213                 com.android.internal.R.mipmap.sym_def_app_icon);
214     }
215
216     Drawable getFullResIcon(Resources resources, int iconId) {
217         try {
218             return resources.getDrawableForDensity(iconId, mIconDpi);
219         } catch (Resources.NotFoundException e) {
220             return getFullResDefaultActivityIcon();
221         }
222     }
223
224     private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
225         Resources resources;
226         try {
227             resources = packageManager.getResourcesForApplication(
228                     info.activityInfo.applicationInfo);
229         } catch (PackageManager.NameNotFoundException e) {
230             resources = null;
231         }
232         if (resources != null) {
233             int iconId = info.activityInfo.getIconResource();
234             if (iconId != 0) {
235                 return getFullResIcon(resources, iconId);
236             }
237         }
238         return getFullResDefaultActivityIcon();
239     }
240
241     Runnable mPreloadTasksRunnable = new Runnable() {
242             public void run() {
243                 loadTasksInBackground();
244             }
245         };
246
247     // additional optimization when we have software system buttons - start loading the recent
248     // tasks on touch down
249     @Override
250     public boolean onTouch(View v, MotionEvent ev) {
251         int action = ev.getAction() & MotionEvent.ACTION_MASK;
252         if (action == MotionEvent.ACTION_DOWN) {
253             preloadRecentTasksList();
254         } else if (action == MotionEvent.ACTION_CANCEL) {
255             cancelPreloadingRecentTasksList();
256         } else if (action == MotionEvent.ACTION_UP) {
257             // Remove the preloader if we haven't called it yet
258             mHandler.removeCallbacks(mPreloadTasksRunnable);
259             if (!v.isPressed()) {
260                 cancelLoadingThumbnailsAndIcons();
261             }
262
263         }
264         return false;
265     }
266
267     public void preloadRecentTasksList() {
268         mHandler.post(mPreloadTasksRunnable);
269     }
270
271     public void cancelPreloadingRecentTasksList() {
272         cancelLoadingThumbnailsAndIcons();
273         mHandler.removeCallbacks(mPreloadTasksRunnable);
274     }
275
276     public void cancelLoadingThumbnailsAndIcons(RecentsPanelView caller) {
277         // Only oblige this request if it comes from the current RecentsPanel
278         // (eg when you rotate, the old RecentsPanel request should be ignored)
279         if (mRecentsPanel == caller) {
280             cancelLoadingThumbnailsAndIcons();
281         }
282     }
283
284
285     private void cancelLoadingThumbnailsAndIcons() {
286         if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
287             return;
288         }
289
290         if (mTaskLoader != null) {
291             mTaskLoader.cancel(false);
292             mTaskLoader = null;
293         }
294         if (mThumbnailLoader != null) {
295             mThumbnailLoader.cancel(false);
296             mThumbnailLoader = null;
297         }
298         mLoadedTasks = null;
299         if (mRecentsPanel != null) {
300             mRecentsPanel.onTaskLoadingCancelled();
301         }
302         mFirstScreenful = false;
303         mState = State.CANCELLED;
304     }
305
306     private void clearFirstTask() {
307         synchronized (mFirstTaskLock) {
308             mFirstTask = null;
309             mFirstTaskLoaded = false;
310         }
311     }
312
313     public void preloadFirstTask() {
314         Thread bgLoad = new Thread() {
315             public void run() {
316                 TaskDescription first = loadFirstTask();
317                 synchronized(mFirstTaskLock) {
318                     if (mCancelPreloadingFirstTask) {
319                         clearFirstTask();
320                     } else {
321                         mFirstTask = first;
322                         mFirstTaskLoaded = true;
323                     }
324                     mPreloadingFirstTask = false;
325                 }
326             }
327         };
328         synchronized(mFirstTaskLock) {
329             if (!mPreloadingFirstTask) {
330                 clearFirstTask();
331                 mPreloadingFirstTask = true;
332                 bgLoad.start();
333             }
334         }
335     }
336
337     public void cancelPreloadingFirstTask() {
338         synchronized(mFirstTaskLock) {
339             if (mPreloadingFirstTask) {
340                 mCancelPreloadingFirstTask = true;
341             } else {
342                 clearFirstTask();
343             }
344         }
345     }
346
347     boolean mPreloadingFirstTask;
348     boolean mCancelPreloadingFirstTask;
349     public TaskDescription getFirstTask() {
350         while(true) {
351             synchronized(mFirstTaskLock) {
352                 if (mFirstTaskLoaded) {
353                     return mFirstTask;
354                 } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) {
355                     mFirstTask = loadFirstTask();
356                     mFirstTaskLoaded = true;
357                     return mFirstTask;
358                 }
359             }
360             try {
361                 Thread.sleep(3);
362             } catch (InterruptedException e) {
363             }
364         }
365     }
366
367     public TaskDescription loadFirstTask() {
368         final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
369
370         final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1,
371                 ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED,
372                 UserHandle.CURRENT.getIdentifier());
373         TaskDescription item = null;
374         if (recentTasks.size() > 0) {
375             ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0);
376
377             Intent intent = new Intent(recentInfo.baseIntent);
378             if (recentInfo.origActivity != null) {
379                 intent.setComponent(recentInfo.origActivity);
380             }
381
382             // Don't load the current home activity.
383             if (isCurrentHomeActivity(intent.getComponent(), null)) {
384                 return null;
385             }
386
387             // Don't load ourselves
388             if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
389                 return null;
390             }
391
392             item = createTaskDescription(recentInfo.id,
393                     recentInfo.persistentId, recentInfo.baseIntent,
394                     recentInfo.origActivity, recentInfo.description);
395             if (item != null) {
396                 loadThumbnailAndIcon(item);
397             }
398             return item;
399         }
400         return null;
401     }
402
403     public void loadTasksInBackground() {
404         loadTasksInBackground(false);
405     }
406     public void loadTasksInBackground(final boolean zeroeth) {
407         if (mState != State.CANCELLED) {
408             return;
409         }
410         mState = State.LOADING;
411         mFirstScreenful = true;
412
413         final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails =
414                 new LinkedBlockingQueue<TaskDescription>();
415         mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() {
416             @Override
417             protected void onProgressUpdate(ArrayList<TaskDescription>... values) {
418                 if (!isCancelled()) {
419                     ArrayList<TaskDescription> newTasks = values[0];
420                     // do a callback to RecentsPanelView to let it know we have more values
421                     // how do we let it know we're all done? just always call back twice
422                     if (mRecentsPanel != null) {
423                         mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful);
424                     }
425                     if (mLoadedTasks == null) {
426                         mLoadedTasks = new ArrayList<TaskDescription>();
427                     }
428                     mLoadedTasks.addAll(newTasks);
429                     mFirstScreenful = false;
430                 }
431             }
432             @Override
433             protected Void doInBackground(Void... params) {
434                 // We load in two stages: first, we update progress with just the first screenful
435                 // of items. Then, we update with the rest of the items
436                 final int origPri = Process.getThreadPriority(Process.myTid());
437                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
438                 final PackageManager pm = mContext.getPackageManager();
439                 final ActivityManager am = (ActivityManager)
440                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
441
442                 final List<ActivityManager.RecentTaskInfo> recentTasks =
443                         am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE
444                         | ActivityManager.RECENT_INCLUDE_RELATED);
445                 int numTasks = recentTasks.size();
446                 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
447                         .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
448
449                 boolean firstScreenful = true;
450                 ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
451
452                 // skip the first task - assume it's either the home screen or the current activity.
453                 final int first = 0;
454                 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
455                     if (isCancelled()) {
456                         break;
457                     }
458                     final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
459
460                     Intent intent = new Intent(recentInfo.baseIntent);
461                     if (recentInfo.origActivity != null) {
462                         intent.setComponent(recentInfo.origActivity);
463                     }
464
465                     // Don't load the current home activity.
466                     if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) {
467                         continue;
468                     }
469
470                     // Don't load ourselves
471                     if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
472                         continue;
473                     }
474
475                     TaskDescription item = createTaskDescription(recentInfo.id,
476                             recentInfo.persistentId, recentInfo.baseIntent,
477                             recentInfo.origActivity, recentInfo.description);
478
479                     if (item != null) {
480                         while (true) {
481                             try {
482                                 tasksWaitingForThumbnails.put(item);
483                                 break;
484                             } catch (InterruptedException e) {
485                             }
486                         }
487                         tasks.add(item);
488                         if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) {
489                             publishProgress(tasks);
490                             tasks = new ArrayList<TaskDescription>();
491                             firstScreenful = false;
492                             //break;
493                         }
494                         ++index;
495                     }
496                 }
497
498                 if (!isCancelled()) {
499                     publishProgress(tasks);
500                     if (firstScreenful) {
501                         // always should publish two updates
502                         publishProgress(new ArrayList<TaskDescription>());
503                     }
504                 }
505
506                 while (true) {
507                     try {
508                         tasksWaitingForThumbnails.put(new TaskDescription());
509                         break;
510                     } catch (InterruptedException e) {
511                     }
512                 }
513
514                 Process.setThreadPriority(origPri);
515                 return null;
516             }
517         };
518         mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
519         loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails);
520     }
521
522     private void loadThumbnailsAndIconsInBackground(
523             final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) {
524         // continually read items from tasksWaitingForThumbnails and load
525         // thumbnails and icons for them. finish thread when cancelled or there
526         // is a null item in tasksWaitingForThumbnails
527         mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() {
528             @Override
529             protected void onProgressUpdate(TaskDescription... values) {
530                 if (!isCancelled()) {
531                     TaskDescription td = values[0];
532                     if (td.isNull()) { // end sentinel
533                         mState = State.LOADED;
534                     } else {
535                         if (mRecentsPanel != null) {
536                             mRecentsPanel.onTaskThumbnailLoaded(td);
537                         }
538                     }
539                 }
540             }
541             @Override
542             protected Void doInBackground(Void... params) {
543                 final int origPri = Process.getThreadPriority(Process.myTid());
544                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
545
546                 while (true) {
547                     if (isCancelled()) {
548                         break;
549                     }
550                     TaskDescription td = null;
551                     while (td == null) {
552                         try {
553                             td = tasksWaitingForThumbnails.take();
554                         } catch (InterruptedException e) {
555                         }
556                     }
557                     if (td.isNull()) { // end sentinel
558                         publishProgress(td);
559                         break;
560                     }
561                     loadThumbnailAndIcon(td);
562
563                     publishProgress(td);
564                 }
565
566                 Process.setThreadPriority(origPri);
567                 return null;
568             }
569         };
570         mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
571     }
572 }