Home | History | Annotate | Download | only in quickstep
      1 /*
      2  * Copyright (C) 2018 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 package com.android.quickstep;
     17 
     18 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
     19 
     20 import android.annotation.TargetApi;
     21 import android.app.ActivityManager;
     22 import android.content.ComponentCallbacks2;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.pm.ActivityInfo;
     26 import android.content.res.Resources;
     27 import android.graphics.drawable.Drawable;
     28 import android.os.Build;
     29 import android.os.Bundle;
     30 import android.os.Looper;
     31 import android.os.RemoteException;
     32 import android.os.UserHandle;
     33 import android.support.annotation.WorkerThread;
     34 import android.util.Log;
     35 import android.util.LruCache;
     36 import android.util.SparseArray;
     37 import android.view.accessibility.AccessibilityManager;
     38 
     39 import com.android.launcher3.MainThreadExecutor;
     40 import com.android.launcher3.R;
     41 import com.android.launcher3.util.Preconditions;
     42 import com.android.systemui.shared.recents.ISystemUiProxy;
     43 import com.android.systemui.shared.recents.model.IconLoader;
     44 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
     45 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
     46 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
     47 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
     48 import com.android.systemui.shared.system.ActivityManagerWrapper;
     49 import com.android.systemui.shared.system.BackgroundExecutor;
     50 import com.android.systemui.shared.system.TaskStackChangeListener;
     51 
     52 import java.util.ArrayList;
     53 import java.util.concurrent.ExecutionException;
     54 import java.util.function.Consumer;
     55 
     56 /**
     57  * Singleton class to load and manage recents model.
     58  */
     59 @TargetApi(Build.VERSION_CODES.O)
     60 public class RecentsModel extends TaskStackChangeListener {
     61     // We do not need any synchronization for this variable as its only written on UI thread.
     62     private static RecentsModel INSTANCE;
     63 
     64     public static RecentsModel getInstance(final Context context) {
     65         if (INSTANCE == null) {
     66             if (Looper.myLooper() == Looper.getMainLooper()) {
     67                 INSTANCE = new RecentsModel(context.getApplicationContext());
     68             } else {
     69                 try {
     70                     return new MainThreadExecutor().submit(
     71                             () -> RecentsModel.getInstance(context)).get();
     72                 } catch (InterruptedException|ExecutionException e) {
     73                     throw new RuntimeException(e);
     74                 }
     75             }
     76         }
     77         return INSTANCE;
     78     }
     79 
     80     private final SparseArray<Bundle> mCachedAssistData = new SparseArray<>(1);
     81     private final ArrayList<AssistDataListener> mAssistDataListeners = new ArrayList<>();
     82 
     83     private final Context mContext;
     84     private final RecentsTaskLoader mRecentsTaskLoader;
     85     private final MainThreadExecutor mMainThreadExecutor;
     86 
     87     private RecentsTaskLoadPlan mLastLoadPlan;
     88     private int mLastLoadPlanId;
     89     private int mTaskChangeId;
     90     private ISystemUiProxy mSystemUiProxy;
     91     private boolean mClearAssistCacheOnStackChange = true;
     92     private final boolean mIsLowRamDevice;
     93     private boolean mPreloadTasksInBackground;
     94     private final AccessibilityManager mAccessibilityManager;
     95 
     96     private RecentsModel(Context context) {
     97         mContext = context;
     98 
     99         ActivityManager activityManager =
    100                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    101         mIsLowRamDevice = activityManager.isLowRamDevice();
    102         mMainThreadExecutor = new MainThreadExecutor();
    103 
    104         Resources res = context.getResources();
    105         mRecentsTaskLoader = new RecentsTaskLoader(mContext,
    106                 res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
    107                 res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0) {
    108 
    109             @Override
    110             protected IconLoader createNewIconLoader(Context context,
    111                     TaskKeyLruCache<Drawable> iconCache,
    112                     LruCache<ComponentName, ActivityInfo> activityInfoCache) {
    113                 return new NormalizedIconLoader(context, iconCache, activityInfoCache);
    114             }
    115         };
    116         mRecentsTaskLoader.startLoader(mContext);
    117         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
    118 
    119         mTaskChangeId = 1;
    120         loadTasks(-1, null);
    121         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
    122     }
    123 
    124     public RecentsTaskLoader getRecentsTaskLoader() {
    125         return mRecentsTaskLoader;
    126     }
    127 
    128     /**
    129      * Preloads the task plan
    130      * @param taskId The running task id or -1
    131      * @param callback The callback to receive the task plan once its complete or null. This is
    132      *                always called on the UI thread.
    133      * @return the request id associated with this call.
    134      */
    135     public int loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
    136         final int requestId = mTaskChangeId;
    137 
    138         // Fail fast if nothing has changed.
    139         if (mLastLoadPlanId == mTaskChangeId) {
    140             if (callback != null) {
    141                 final RecentsTaskLoadPlan plan = mLastLoadPlan;
    142                 mMainThreadExecutor.execute(() -> callback.accept(plan));
    143             }
    144             return requestId;
    145         }
    146 
    147         BackgroundExecutor.get().submit(() -> {
    148             // Preload the plan
    149             RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext);
    150             PreloadOptions opts = new PreloadOptions();
    151             opts.loadTitles = mAccessibilityManager.isEnabled();
    152             loadPlan.preloadPlan(opts, mRecentsTaskLoader, taskId, UserHandle.myUserId());
    153             // Set the load plan on UI thread
    154             mMainThreadExecutor.execute(() -> {
    155                 mLastLoadPlan = loadPlan;
    156                 mLastLoadPlanId = requestId;
    157 
    158                 if (callback != null) {
    159                     callback.accept(loadPlan);
    160                 }
    161             });
    162         });
    163         return requestId;
    164     }
    165 
    166     public void setPreloadTasksInBackground(boolean preloadTasksInBackground) {
    167         mPreloadTasksInBackground = preloadTasksInBackground && !mIsLowRamDevice;
    168     }
    169 
    170     @Override
    171     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
    172         mTaskChangeId++;
    173     }
    174 
    175     @Override
    176     public void onActivityUnpinned() {
    177         mTaskChangeId++;
    178     }
    179 
    180     @Override
    181     public void onTaskStackChanged() {
    182         mTaskChangeId++;
    183 
    184         Preconditions.assertUIThread();
    185         if (mClearAssistCacheOnStackChange) {
    186             mCachedAssistData.clear();
    187         } else {
    188             mClearAssistCacheOnStackChange = true;
    189         }
    190     }
    191 
    192     @Override
    193     public void onTaskStackChangedBackground() {
    194         int userId = UserHandle.myUserId();
    195         if (!mPreloadTasksInBackground || !checkCurrentOrManagedUserId(userId, mContext)) {
    196             // TODO: Only register this for the current user
    197             return;
    198         }
    199 
    200         // Preload a fixed number of task icons/thumbnails in the background
    201         ActivityManager.RunningTaskInfo runningTaskInfo =
    202                 ActivityManagerWrapper.getInstance().getRunningTask();
    203         RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
    204         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
    205         launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
    206         launchOpts.numVisibleTasks = 2;
    207         launchOpts.numVisibleTaskThumbnails = 2;
    208         launchOpts.onlyLoadForCache = true;
    209         launchOpts.onlyLoadPausedActivities = true;
    210         launchOpts.loadThumbnails = true;
    211         PreloadOptions preloadOpts = new PreloadOptions();
    212         preloadOpts.loadTitles = mAccessibilityManager.isEnabled();
    213         plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
    214         mRecentsTaskLoader.loadTasks(plan, launchOpts);
    215     }
    216 
    217     public boolean isLoadPlanValid(int resultId) {
    218         return mTaskChangeId == resultId;
    219     }
    220 
    221     public RecentsTaskLoadPlan getLastLoadPlan() {
    222         return mLastLoadPlan;
    223     }
    224 
    225     public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
    226         mSystemUiProxy = systemUiProxy;
    227     }
    228 
    229     public ISystemUiProxy getSystemUiProxy() {
    230         return mSystemUiProxy;
    231     }
    232 
    233     public void onStart() {
    234         mRecentsTaskLoader.startLoader(mContext);
    235         mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
    236     }
    237 
    238     public void onTrimMemory(int level) {
    239         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
    240             // We already stop the loader in UI_HIDDEN, so stop the high res loader as well
    241             mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
    242         }
    243         mRecentsTaskLoader.onTrimMemory(level);
    244     }
    245 
    246     public void onOverviewShown(boolean fromHome, String tag) {
    247         if (mSystemUiProxy == null) {
    248             return;
    249         }
    250         try {
    251             mSystemUiProxy.onOverviewShown(fromHome);
    252         } catch (RemoteException e) {
    253             Log.w(tag,
    254                     "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")
    255                             + ": ", e);
    256         }
    257     }
    258 
    259     @WorkerThread
    260     public void preloadAssistData(int taskId, Bundle data) {
    261         mMainThreadExecutor.execute(() -> {
    262             mCachedAssistData.put(taskId, data);
    263             // We expect a stack change callback after the assist data is set. So ignore the
    264             // very next stack change callback.
    265             mClearAssistCacheOnStackChange = false;
    266 
    267             int count = mAssistDataListeners.size();
    268             for (int i = 0; i < count; i++) {
    269                 mAssistDataListeners.get(i).onAssistDataReceived(taskId);
    270             }
    271         });
    272     }
    273 
    274     public Bundle getAssistData(int taskId) {
    275         Preconditions.assertUIThread();
    276         return mCachedAssistData.get(taskId);
    277     }
    278 
    279     public void addAssistDataListener(AssistDataListener listener) {
    280         mAssistDataListeners.add(listener);
    281     }
    282 
    283     public void removeAssistDataListener(AssistDataListener listener) {
    284         mAssistDataListeners.remove(listener);
    285     }
    286 
    287     /**
    288      * Callback for receiving assist data
    289      */
    290     public interface AssistDataListener {
    291 
    292         void onAssistDataReceived(int taskId);
    293     }
    294 }
    295