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