Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ab48b681 authored by Michael Jurka's avatar Michael Jurka
Browse files

Refactoring loading of recent apps

- fix bugs 5278690 and 5432097
- no longer forcing a reload when screen is rotated
- moving recents loading code into a seaprate class
- changing variable names to use "Task" rather than "Activity" in Recent Apps

Change-Id: Ib0c8c5d537cf9d46d65b2ccb790015b601bb1bf1
parent 99f36683
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -121,6 +121,7 @@ import android.view.View;
        createAnimation(appearing);

        mContentView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        mContentView.buildLayer();
        mContentAnim.start();

        mVisible = appearing;
+309 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.recent;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Process;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LruCache;

import com.android.systemui.R;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.tablet.TabletStatusBar;

public class RecentTasksLoader {
    static final String TAG = "RecentTasksLoader";
    static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;

    private static final int DISPLAY_TASKS = 20;
    private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps

    private Context mContext;
    private RecentsPanelView mRecentsPanel;

    private AsyncTask<Void, Integer, Void> mThumbnailLoader;
    private final Handler mHandler;

    private int mIconDpi;
    private Bitmap mDefaultThumbnailBackground;

    public RecentTasksLoader(Context context) {
        mContext = context;

        final Resources res = context.getResources();

        // get the icon size we want -- on tablets, we use bigger icons
        boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
        int density = res.getDisplayMetrics().densityDpi;
        if (isTablet) {
            if (density == DisplayMetrics.DENSITY_LOW) {
                mIconDpi = DisplayMetrics.DENSITY_MEDIUM;
            } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
                mIconDpi = DisplayMetrics.DENSITY_HIGH;
            } else if (density == DisplayMetrics.DENSITY_HIGH) {
                mIconDpi = DisplayMetrics.DENSITY_XHIGH;
            } else if (density == DisplayMetrics.DENSITY_XHIGH) {
                // We'll need to use a denser icon, or some sort of a mipmap
                mIconDpi = DisplayMetrics.DENSITY_XHIGH;
            }
        } else {
            mIconDpi = res.getDisplayMetrics().densityDpi;
        }
        mIconDpi = isTablet ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;

        // Render the default thumbnail background
        int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width);
        int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height);
        int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);

        mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(mDefaultThumbnailBackground);
        c.drawColor(color);

        // If we're using the cache, begin listening to the activity manager for
        // updated thumbnails
        final ActivityManager am = (ActivityManager)
                mContext.getSystemService(Context.ACTIVITY_SERVICE);

        mHandler = new Handler();
    }

    public void setRecentsPanel(RecentsPanelView recentsPanel) {
        mRecentsPanel = recentsPanel;
    }

    // Create an TaskDescription, returning null if the title or icon is null, or if it's the
    // home activity
    TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
            ComponentName origActivity, CharSequence description, ActivityInfo homeInfo) {
        Intent intent = new Intent(baseIntent);
        if (origActivity != null) {
            intent.setComponent(origActivity);
        }
        final PackageManager pm = mContext.getPackageManager();
        if (homeInfo == null) {
            homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
            .resolveActivityInfo(pm, 0);
        }

        intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
                | Intent.FLAG_ACTIVITY_NEW_TASK);
        final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
        if (resolveInfo != null) {
            final ActivityInfo info = resolveInfo.activityInfo;
            final String title = info.loadLabel(pm).toString();
            Drawable icon = getFullResIcon(resolveInfo, pm);

            if (title != null && title.length() > 0 && icon != null) {
                if (DEBUG) Log.v(TAG, "creating activity desc for id="
                        + persistentTaskId + ", label=" + title);

                TaskDescription item = new TaskDescription(taskId,
                        persistentTaskId, resolveInfo, baseIntent, info.packageName,
                        description);
                item.setLabel(title);
                item.setIcon(icon);

                // Don't load the current home activity.
                if (homeInfo != null
                        && homeInfo.packageName.equals(intent.getComponent().getPackageName())
                        && homeInfo.name.equals(intent.getComponent().getClassName())) {
                    return null;
                }

                return item;
            } else {
                if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
            }
        }
        return null;
    }

    void loadThumbnail(TaskDescription td) {
        final ActivityManager am = (ActivityManager)
                mContext.getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(td.persistentTaskId);

        if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
                + td + ": " + thumbs.mainThumbnail);
        synchronized (td) {
            if (thumbs != null && thumbs.mainThumbnail != null) {
                td.setThumbnail(thumbs.mainThumbnail);
            } else {
                td.setThumbnail(mDefaultThumbnailBackground);
            }
        }
    }

    Drawable getFullResDefaultActivityIcon() {
        return getFullResIcon(Resources.getSystem(),
                com.android.internal.R.mipmap.sym_def_app_icon);
    }

    Drawable getFullResIcon(Resources resources, int iconId) {
        try {
            return resources.getDrawableForDensity(iconId, mIconDpi);
        } catch (Resources.NotFoundException e) {
            return getFullResDefaultActivityIcon();
        }
    }

    private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
        Resources resources;
        try {
            resources = packageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);
        } catch (PackageManager.NameNotFoundException e) {
            resources = null;
        }
        if (resources != null) {
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
                return getFullResIcon(resources, iconId);
            }
        }
        return getFullResDefaultActivityIcon();
    }

    public void cancelLoadingThumbnails() {
        if (mThumbnailLoader != null) {
            mThumbnailLoader.cancel(false);
            mThumbnailLoader = null;
        }
    }

    // return a snapshot of the current list of recent apps
    ArrayList<TaskDescription> getRecentTasks() {
        cancelLoadingThumbnails();

        ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
        final PackageManager pm = mContext.getPackageManager();
        final ActivityManager am = (ActivityManager)
                mContext.getSystemService(Context.ACTIVITY_SERVICE);

        final List<ActivityManager.RecentTaskInfo> recentTasks =
                am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);

        ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
                    .resolveActivityInfo(pm, 0);

        HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();
        int numTasks = recentTasks.size();

        // skip the first task - assume it's either the home screen or the current activity.
        final int first = 1;
        recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);
        for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
            final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);

            TaskDescription item = createTaskDescription(recentInfo.id,
                    recentInfo.persistentId, recentInfo.baseIntent,
                    recentInfo.origActivity, recentInfo.description, homeInfo);

            if (item != null) {
                tasks.add(item);
                ++index;
            }
        }

        // when we're not using the TaskDescription cache, we load the thumbnails in the
        // background
        loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));
        return tasks;
    }

    private void loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions) {
        if (descriptions.size() > 0) {
            if (DEBUG) Log.v(TAG, "Showing " + descriptions.size() + " tasks");
            loadThumbnail(descriptions.get(0));
            if (descriptions.size() > 1) {
                mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
                    @Override
                    protected void onProgressUpdate(Integer... values) {
                        final TaskDescription td = descriptions.get(values[0]);
                        if (!isCancelled()) {
                            mRecentsPanel.onTaskThumbnailLoaded(td);
                        }
                        // This is to prevent the loader thread from getting ahead
                        // of our UI updates.
                        mHandler.post(new Runnable() {
                            @Override public void run() {
                                synchronized (td) {
                                    td.notifyAll();
                                }
                            }
                        });
                    }

                    @Override
                    protected Void doInBackground(Void... params) {
                        final int origPri = Process.getThreadPriority(Process.myTid());
                        Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
                        long nextTime = SystemClock.uptimeMillis();
                        for (int i=1; i<descriptions.size(); i++) {
                            TaskDescription td = descriptions.get(i);
                            loadThumbnail(td);
                            long now = SystemClock.uptimeMillis();
                            nextTime += 150;
                            if (nextTime > now) {
                                try {
                                    Thread.sleep(nextTime-now);
                                } catch (InterruptedException e) {
                                }
                            }

                            if (isCancelled()) {
                                break;
                            }
                            synchronized (td) {
                                publishProgress(i);
                                try {
                                    td.wait(500);
                                } catch (InterruptedException e) {
                                }
                            }
                        }
                        Process.setThreadPriority(origPri);
                        return null;
                    }
                };
                mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            }
        }
    }

}
+3 −3
Original line number Diff line number Diff line
@@ -32,14 +32,14 @@ import android.widget.LinearLayout;

import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter;
import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;

public class RecentsHorizontalScrollView extends HorizontalScrollView
    implements SwipeHelper.Callback {
    private static final String TAG = RecentsPanelView.TAG;
    private static final boolean DEBUG = RecentsPanelView.DEBUG;
    private LinearLayout mLinearLayout;
    private ActivityDescriptionAdapter mAdapter;
    private TaskDescriptionAdapter mAdapter;
    private RecentsCallback mCallback;
    protected int mLastScrollPosition;
    private SwipeHelper mSwipeHelper;
@@ -304,7 +304,7 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView
        }
    }

    public void setAdapter(ActivityDescriptionAdapter adapter) {
    public void setAdapter(TaskDescriptionAdapter adapter) {
        mAdapter = adapter;
        mAdapter.registerDataSetObserver(new DataSetObserver() {
            public void onChanged() {
Loading