| /* |
| * Copyright (C) 2017 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.recents.model; |
| |
| import static android.os.Process.setThreadPriority; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.SystemClock; |
| import android.util.ArraySet; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.systemui.recents.misc.SystemServicesProxy; |
| import com.android.systemui.recents.model.Task.TaskCallbacks; |
| |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| |
| /** |
| * Loader class that loads full-resolution thumbnails when appropriate. |
| */ |
| public class HighResThumbnailLoader implements TaskCallbacks { |
| |
| @GuardedBy("mLoadQueue") |
| private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>(); |
| @GuardedBy("mLoadQueue") |
| private final ArraySet<Task> mLoadingTasks = new ArraySet<>(); |
| @GuardedBy("mLoadQueue") |
| private boolean mLoaderIdling; |
| |
| private final ArrayList<Task> mVisibleTasks = new ArrayList<>(); |
| private final Thread mLoadThread; |
| private final Handler mMainThreadHandler; |
| private final SystemServicesProxy mSystemServicesProxy; |
| private boolean mLoading; |
| private boolean mVisible; |
| private boolean mFlingingFast; |
| |
| public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper) { |
| mMainThreadHandler = new Handler(looper); |
| mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader"); |
| mLoadThread.start(); |
| mSystemServicesProxy = ssp; |
| } |
| |
| public void setVisible(boolean visible) { |
| mVisible = visible; |
| updateLoading(); |
| } |
| |
| public void setFlingingFast(boolean flingingFast) { |
| if (mFlingingFast == flingingFast) { |
| return; |
| } |
| mFlingingFast = flingingFast; |
| updateLoading(); |
| } |
| |
| @VisibleForTesting |
| boolean isLoading() { |
| return mLoading; |
| } |
| |
| private void updateLoading() { |
| setLoading(mVisible && !mFlingingFast); |
| } |
| |
| private void setLoading(boolean loading) { |
| if (loading == mLoading) { |
| return; |
| } |
| synchronized (mLoadQueue) { |
| mLoading = loading; |
| if (!loading) { |
| stopLoading(); |
| } else { |
| startLoading(); |
| } |
| } |
| } |
| |
| @GuardedBy("mLoadQueue") |
| private void startLoading() { |
| for (int i = mVisibleTasks.size() - 1; i >= 0; i--) { |
| Task t = mVisibleTasks.get(i); |
| if ((t.thumbnail == null || t.thumbnail.reducedResolution) |
| && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) { |
| mLoadQueue.add(t); |
| } |
| } |
| mLoadQueue.notifyAll(); |
| } |
| |
| @GuardedBy("mLoadQueue") |
| private void stopLoading() { |
| mLoadQueue.clear(); |
| mLoadQueue.notifyAll(); |
| } |
| |
| /** |
| * Needs to be called when a task becomes visible. Note that this is different from |
| * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it |
| * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data |
| * has been updated. |
| */ |
| public void onTaskVisible(Task t) { |
| t.addCallback(this); |
| mVisibleTasks.add(t); |
| if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) { |
| synchronized (mLoadQueue) { |
| mLoadQueue.add(t); |
| mLoadQueue.notifyAll(); |
| } |
| } |
| } |
| |
| /** |
| * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is |
| * different from {@link TaskCallbacks#onTaskDataUnloaded()} |
| */ |
| public void onTaskInvisible(Task t) { |
| t.removeCallback(this); |
| mVisibleTasks.remove(t); |
| synchronized (mLoadQueue) { |
| mLoadQueue.remove(t); |
| } |
| } |
| |
| @VisibleForTesting |
| void waitForLoaderIdle() { |
| while (true) { |
| synchronized (mLoadQueue) { |
| if (mLoadQueue.isEmpty() && mLoaderIdling) { |
| return; |
| } |
| } |
| SystemClock.sleep(100); |
| } |
| } |
| |
| @Override |
| public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { |
| if (thumbnailData != null && !thumbnailData.reducedResolution) { |
| synchronized (mLoadQueue) { |
| mLoadQueue.remove(task); |
| } |
| } |
| } |
| |
| @Override |
| public void onTaskDataUnloaded() { |
| } |
| |
| @Override |
| public void onTaskStackIdChanged() { |
| } |
| |
| private final Runnable mLoader = new Runnable() { |
| |
| @Override |
| public void run() { |
| setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1); |
| while (true) { |
| Task next = null; |
| synchronized (mLoadQueue) { |
| if (!mLoading || mLoadQueue.isEmpty()) { |
| try { |
| mLoaderIdling = true; |
| mLoadQueue.wait(); |
| mLoaderIdling = false; |
| } catch (InterruptedException e) { |
| // Don't care. |
| } |
| } else { |
| next = mLoadQueue.poll(); |
| if (next != null) { |
| mLoadingTasks.add(next); |
| } |
| } |
| } |
| if (next != null) { |
| loadTask(next); |
| } |
| } |
| } |
| |
| private void loadTask(Task t) { |
| ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id, |
| false /* reducedResolution */); |
| mMainThreadHandler.post(() -> { |
| synchronized (mLoadQueue) { |
| mLoadingTasks.remove(t); |
| } |
| if (mVisibleTasks.contains(t)) { |
| t.notifyTaskDataLoaded(thumbnail, t.icon); |
| } |
| }); |
| } |
| }; |
| } |