1 /* 2 * Copyright (C) 2017 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.shared.recents.model; 18 19 import static android.os.Process.setThreadPriority; 20 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.SystemClock; 24 import android.util.ArraySet; 25 26 import com.android.internal.annotations.GuardedBy; 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.systemui.shared.recents.model.Task.TaskCallbacks; 29 import com.android.systemui.shared.system.ActivityManagerWrapper; 30 31 import java.util.ArrayDeque; 32 import java.util.ArrayList; 33 34 /** 35 * Loader class that loads full-resolution thumbnails when appropriate. 36 */ 37 public class HighResThumbnailLoader implements TaskCallbacks { 38 39 private final ActivityManagerWrapper mActivityManager; 40 41 @GuardedBy("mLoadQueue") 42 private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>(); 43 @GuardedBy("mLoadQueue") 44 private final ArraySet<Task> mLoadingTasks = new ArraySet<>(); 45 @GuardedBy("mLoadQueue") 46 private boolean mLoaderIdling; 47 48 private final ArrayList<Task> mVisibleTasks = new ArrayList<>(); 49 50 private final Thread mLoadThread; 51 private final Handler mMainThreadHandler; 52 private final boolean mIsLowRamDevice; 53 private boolean mLoading; 54 private boolean mVisible; 55 private boolean mFlingingFast; 56 private boolean mTaskLoadQueueIdle; 57 HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper, boolean isLowRamDevice)58 public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper, 59 boolean isLowRamDevice) { 60 mActivityManager = activityManager; 61 mMainThreadHandler = new Handler(looper); 62 mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader"); 63 mLoadThread.start(); 64 mIsLowRamDevice = isLowRamDevice; 65 } 66 setVisible(boolean visible)67 public void setVisible(boolean visible) { 68 if (mIsLowRamDevice) { 69 return; 70 } 71 mVisible = visible; 72 updateLoading(); 73 } 74 setFlingingFast(boolean flingingFast)75 public void setFlingingFast(boolean flingingFast) { 76 if (mFlingingFast == flingingFast || mIsLowRamDevice) { 77 return; 78 } 79 mFlingingFast = flingingFast; 80 updateLoading(); 81 } 82 83 /** 84 * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not 85 * starting this queue until the other queue is idling. 86 */ setTaskLoadQueueIdle(boolean idle)87 public void setTaskLoadQueueIdle(boolean idle) { 88 if (mIsLowRamDevice) { 89 return; 90 } 91 mTaskLoadQueueIdle = idle; 92 updateLoading(); 93 } 94 95 @VisibleForTesting isLoading()96 boolean isLoading() { 97 return mLoading; 98 } 99 updateLoading()100 private void updateLoading() { 101 setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle); 102 } 103 setLoading(boolean loading)104 private void setLoading(boolean loading) { 105 if (loading == mLoading) { 106 return; 107 } 108 synchronized (mLoadQueue) { 109 mLoading = loading; 110 if (!loading) { 111 stopLoading(); 112 } else { 113 startLoading(); 114 } 115 } 116 } 117 118 @GuardedBy("mLoadQueue") startLoading()119 private void startLoading() { 120 for (int i = mVisibleTasks.size() - 1; i >= 0; i--) { 121 Task t = mVisibleTasks.get(i); 122 if ((t.thumbnail == null || t.thumbnail.reducedResolution) 123 && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) { 124 mLoadQueue.add(t); 125 } 126 } 127 mLoadQueue.notifyAll(); 128 } 129 130 @GuardedBy("mLoadQueue") stopLoading()131 private void stopLoading() { 132 mLoadQueue.clear(); 133 mLoadQueue.notifyAll(); 134 } 135 136 /** 137 * Needs to be called when a task becomes visible. Note that this is different from 138 * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it 139 * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data 140 * has been updated. 141 */ onTaskVisible(Task t)142 public void onTaskVisible(Task t) { 143 t.addCallback(this); 144 mVisibleTasks.add(t); 145 if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) { 146 synchronized (mLoadQueue) { 147 mLoadQueue.add(t); 148 mLoadQueue.notifyAll(); 149 } 150 } 151 } 152 153 /** 154 * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is 155 * different from {@link TaskCallbacks#onTaskDataUnloaded()} 156 */ onTaskInvisible(Task t)157 public void onTaskInvisible(Task t) { 158 t.removeCallback(this); 159 mVisibleTasks.remove(t); 160 synchronized (mLoadQueue) { 161 mLoadQueue.remove(t); 162 } 163 } 164 165 @VisibleForTesting waitForLoaderIdle()166 void waitForLoaderIdle() { 167 while (true) { 168 synchronized (mLoadQueue) { 169 if (mLoadQueue.isEmpty() && mLoaderIdling) { 170 return; 171 } 172 } 173 SystemClock.sleep(100); 174 } 175 } 176 177 @Override onTaskDataLoaded(Task task, ThumbnailData thumbnailData)178 public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { 179 if (thumbnailData != null && !thumbnailData.reducedResolution) { 180 synchronized (mLoadQueue) { 181 mLoadQueue.remove(task); 182 } 183 } 184 } 185 186 @Override onTaskDataUnloaded()187 public void onTaskDataUnloaded() { 188 } 189 190 @Override onTaskWindowingModeChanged()191 public void onTaskWindowingModeChanged() { 192 } 193 194 private final Runnable mLoader = new Runnable() { 195 196 @Override 197 public void run() { 198 setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1); 199 while (true) { 200 Task next = null; 201 synchronized (mLoadQueue) { 202 if (!mLoading || mLoadQueue.isEmpty()) { 203 try { 204 mLoaderIdling = true; 205 mLoadQueue.wait(); 206 mLoaderIdling = false; 207 } catch (InterruptedException e) { 208 // Don't care. 209 } 210 } else { 211 next = mLoadQueue.poll(); 212 if (next != null) { 213 mLoadingTasks.add(next); 214 } 215 } 216 } 217 if (next != null) { 218 loadTask(next); 219 } 220 } 221 } 222 223 private void loadTask(Task t) { 224 ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id, 225 false /* reducedResolution */); 226 mMainThreadHandler.post(() -> { 227 synchronized (mLoadQueue) { 228 mLoadingTasks.remove(t); 229 } 230 if (mVisibleTasks.contains(t)) { 231 t.notifyTaskDataLoaded(thumbnail, t.icon); 232 } 233 }); 234 } 235 }; 236 } 237