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.recents.model; 18 19 import android.content.Context; 20 import android.graphics.drawable.Drawable; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.util.Log; 24 25 import com.android.systemui.shared.recents.model.IconLoader; 26 import com.android.systemui.shared.recents.model.Task; 27 import com.android.systemui.shared.recents.model.ThumbnailData; 28 import com.android.systemui.shared.system.ActivityManagerWrapper; 29 30 /** 31 * Background task resource loader 32 */ 33 class BackgroundTaskLoader implements Runnable { 34 static String TAG = "BackgroundTaskLoader"; 35 static boolean DEBUG = false; 36 37 private Context mContext; 38 private final HandlerThread mLoadThread; 39 private final Handler mLoadThreadHandler; 40 private final Handler mMainThreadHandler; 41 42 private final TaskResourceLoadQueue mLoadQueue; 43 private final IconLoader mIconLoader; 44 45 private boolean mStarted; 46 private boolean mCancelled; 47 private boolean mWaitingOnLoadQueue; 48 49 private final OnIdleChangedListener mOnIdleChangedListener; 50 51 /** Constructor, creates a new loading thread that loads task resources in the background */ BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, IconLoader iconLoader, OnIdleChangedListener onIdleChangedListener)52 public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, 53 IconLoader iconLoader, OnIdleChangedListener onIdleChangedListener) { 54 mLoadQueue = loadQueue; 55 mIconLoader = iconLoader; 56 mMainThreadHandler = new Handler(); 57 mOnIdleChangedListener = onIdleChangedListener; 58 mLoadThread = new HandlerThread("Recents-TaskResourceLoader", 59 android.os.Process.THREAD_PRIORITY_BACKGROUND); 60 mLoadThread.start(); 61 mLoadThreadHandler = new Handler(mLoadThread.getLooper()); 62 } 63 64 /** Restarts the loader thread */ start(Context context)65 void start(Context context) { 66 mContext = context; 67 mCancelled = false; 68 if (!mStarted) { 69 // Start loading on the load thread 70 mStarted = true; 71 mLoadThreadHandler.post(this); 72 } else { 73 // Notify the load thread to start loading again 74 synchronized (mLoadThread) { 75 mLoadThread.notifyAll(); 76 } 77 } 78 } 79 80 /** Requests the loader thread to stop after the current iteration */ stop()81 void stop() { 82 // Mark as cancelled for the thread to pick up 83 mCancelled = true; 84 // If we are waiting for the load queue for more tasks, then we can just reset the 85 // Context now, since nothing is using it 86 if (mWaitingOnLoadQueue) { 87 mContext = null; 88 } 89 } 90 91 @Override run()92 public void run() { 93 while (true) { 94 if (mCancelled) { 95 // We have to unset the context here, since the background thread may be using it 96 // when we call stop() 97 mContext = null; 98 // If we are cancelled, then wait until we are started again 99 synchronized(mLoadThread) { 100 try { 101 mLoadThread.wait(); 102 } catch (InterruptedException ie) { 103 ie.printStackTrace(); 104 } 105 } 106 } else { 107 // If we've stopped the loader, then fall through to the above logic to wait on 108 // the load thread 109 processLoadQueueItem(); 110 111 // If there are no other items in the list, then just wait until something is added 112 if (!mCancelled && mLoadQueue.isEmpty()) { 113 synchronized(mLoadQueue) { 114 try { 115 mWaitingOnLoadQueue = true; 116 mMainThreadHandler.post(new Runnable() { 117 @Override 118 public void run() { 119 mOnIdleChangedListener.onIdleChanged(true); 120 } 121 }); 122 mLoadQueue.wait(); 123 mMainThreadHandler.post(new Runnable() { 124 @Override 125 public void run() { 126 mOnIdleChangedListener.onIdleChanged(false); 127 } 128 }); 129 mWaitingOnLoadQueue = false; 130 } catch (InterruptedException ie) { 131 ie.printStackTrace(); 132 } 133 } 134 } 135 } 136 } 137 } 138 139 /** 140 * This needs to be in a separate method to work around an surprising interpreter behavior: 141 * The register will keep the local reference to cachedThumbnailData even if it falls out of 142 * scope. Putting it into a method fixes this issue. 143 */ processLoadQueueItem()144 private void processLoadQueueItem() { 145 // Load the next item from the queue 146 final Task t = mLoadQueue.nextTask(); 147 if (t != null) { 148 final Drawable icon = mIconLoader.getIcon(t); 149 if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); 150 final ThumbnailData thumbnailData = 151 ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id, 152 true /* reducedResolution */); 153 154 if (!mCancelled) { 155 // Notify that the task data has changed 156 mMainThreadHandler.post(new Runnable() { 157 @Override 158 public void run() { 159 t.notifyTaskDataLoaded(thumbnailData, icon); 160 } 161 }); 162 } 163 } 164 } 165 166 interface OnIdleChangedListener { onIdleChanged(boolean idle)167 void onIdleChanged(boolean idle); 168 } 169 } 170