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