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