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