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.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(
    371                 1, ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
    372         TaskDescription item = null;
    373         if (recentTasks.size() > 0) {
    374             ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0);
    375 
    376             Intent intent = new Intent(recentInfo.baseIntent);
    377             if (recentInfo.origActivity != null) {
    378                 intent.setComponent(recentInfo.origActivity);
    379             }
    380 
    381             // Don't load the current home activity.
    382             if (isCurrentHomeActivity(intent.getComponent(), null)) {
    383                 return null;
    384             }
    385 
    386             // Don't load ourselves
    387             if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
    388                 return null;
    389             }
    390 
    391             item = createTaskDescription(recentInfo.id,
    392                     recentInfo.persistentId, recentInfo.baseIntent,
    393                     recentInfo.origActivity, recentInfo.description);
    394             if (item != null) {
    395                 loadThumbnailAndIcon(item);
    396             }
    397             return item;
    398         }
    399         return null;
    400     }
    401 
    402     public void loadTasksInBackground() {
    403         loadTasksInBackground(false);
    404     }
    405     public void loadTasksInBackground(final boolean zeroeth) {
    406         if (mState != State.CANCELLED) {
    407             return;
    408         }
    409         mState = State.LOADING;
    410         mFirstScreenful = true;
    411 
    412         final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails =
    413                 new LinkedBlockingQueue<TaskDescription>();
    414         mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() {
    415             @Override
    416             protected void onProgressUpdate(ArrayList<TaskDescription>... values) {
    417                 if (!isCancelled()) {
    418                     ArrayList<TaskDescription> newTasks = values[0];
    419                     // do a callback to RecentsPanelView to let it know we have more values
    420                     // how do we let it know we're all done? just always call back twice
    421                     if (mRecentsPanel != null) {
    422                         mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful);
    423                     }
    424                     if (mLoadedTasks == null) {
    425                         mLoadedTasks = new ArrayList<TaskDescription>();
    426                     }
    427                     mLoadedTasks.addAll(newTasks);
    428                     mFirstScreenful = false;
    429                 }
    430             }
    431             @Override
    432             protected Void doInBackground(Void... params) {
    433                 // We load in two stages: first, we update progress with just the first screenful
    434                 // of items. Then, we update with the rest of the items
    435                 final int origPri = Process.getThreadPriority(Process.myTid());
    436                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    437                 final PackageManager pm = mContext.getPackageManager();
    438                 final ActivityManager am = (ActivityManager)
    439                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
    440 
    441                 final List<ActivityManager.RecentTaskInfo> recentTasks =
    442                         am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
    443                 int numTasks = recentTasks.size();
    444                 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
    445                         .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
    446 
    447                 boolean firstScreenful = true;
    448                 ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
    449 
    450                 // skip the first task - assume it's either the home screen or the current activity.
    451                 final int first = 0;
    452                 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
    453                     if (isCancelled()) {
    454                         break;
    455                     }
    456                     final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
    457 
    458                     Intent intent = new Intent(recentInfo.baseIntent);
    459                     if (recentInfo.origActivity != null) {
    460                         intent.setComponent(recentInfo.origActivity);
    461                     }
    462 
    463                     // Don't load the current home activity.
    464                     if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) {
    465                         continue;
    466                     }
    467 
    468                     // Don't load ourselves
    469                     if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
    470                         continue;
    471                     }
    472 
    473                     TaskDescription item = createTaskDescription(recentInfo.id,
    474                             recentInfo.persistentId, recentInfo.baseIntent,
    475                             recentInfo.origActivity, recentInfo.description);
    476 
    477                     if (item != null) {
    478                         while (true) {
    479                             try {
    480                                 tasksWaitingForThumbnails.put(item);
    481                                 break;
    482                             } catch (InterruptedException e) {
    483                             }
    484                         }
    485                         tasks.add(item);
    486                         if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) {
    487                             publishProgress(tasks);
    488                             tasks = new ArrayList<TaskDescription>();
    489                             firstScreenful = false;
    490                             //break;
    491                         }
    492                         ++index;
    493                     }
    494                 }
    495 
    496                 if (!isCancelled()) {
    497                     publishProgress(tasks);
    498                     if (firstScreenful) {
    499                         // always should publish two updates
    500                         publishProgress(new ArrayList<TaskDescription>());
    501                     }
    502                 }
    503 
    504                 while (true) {
    505                     try {
    506                         tasksWaitingForThumbnails.put(new TaskDescription());
    507                         break;
    508                     } catch (InterruptedException e) {
    509                     }
    510                 }
    511 
    512                 Process.setThreadPriority(origPri);
    513                 return null;
    514             }
    515         };
    516         mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    517         loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails);
    518     }
    519 
    520     private void loadThumbnailsAndIconsInBackground(
    521             final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) {
    522         // continually read items from tasksWaitingForThumbnails and load
    523         // thumbnails and icons for them. finish thread when cancelled or there
    524         // is a null item in tasksWaitingForThumbnails
    525         mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() {
    526             @Override
    527             protected void onProgressUpdate(TaskDescription... values) {
    528                 if (!isCancelled()) {
    529                     TaskDescription td = values[0];
    530                     if (td.isNull()) { // end sentinel
    531                         mState = State.LOADED;
    532                     } else {
    533                         if (mRecentsPanel != null) {
    534                             mRecentsPanel.onTaskThumbnailLoaded(td);
    535                         }
    536                     }
    537                 }
    538             }
    539             @Override
    540             protected Void doInBackground(Void... params) {
    541                 final int origPri = Process.getThreadPriority(Process.myTid());
    542                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    543 
    544                 while (true) {
    545                     if (isCancelled()) {
    546                         break;
    547                     }
    548                     TaskDescription td = null;
    549                     while (td == null) {
    550                         try {
    551                             td = tasksWaitingForThumbnails.take();
    552                         } catch (InterruptedException e) {
    553                         }
    554                     }
    555                     if (td.isNull()) { // end sentinel
    556                         publishProgress(td);
    557                         break;
    558                     }
    559                     loadThumbnailAndIcon(td);
    560 
    561                     publishProgress(td);
    562                 }
    563 
    564                 Process.setThreadPriority(origPri);
    565                 return null;
    566             }
    567         };
    568         mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    569     }
    570 }
    571