1 /**
2  * Copyright (C) 2015 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.launcher3.util;
18 
19 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
20 
21 import android.os.Process;
22 import android.view.View;
23 import android.view.View.OnAttachStateChangeListener;
24 import android.view.ViewTreeObserver.OnDrawListener;
25 
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.launcher3.Launcher;
29 
30 import java.util.ArrayList;
31 import java.util.concurrent.Executor;
32 import java.util.function.Consumer;
33 
34 /**
35  * An executor which runs all the tasks after the first onDraw is called on the target view.
36  */
37 public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
38         OnAttachStateChangeListener {
39 
40     private final ArrayList<Runnable> mTasks = new ArrayList<>();
41 
42     private Consumer<ViewOnDrawExecutor> mOnClearCallback;
43     private View mAttachedView;
44     private boolean mCompleted;
45 
46     private boolean mLoadAnimationCompleted;
47     private boolean mFirstDrawCompleted;
48 
attachTo(Launcher launcher)49     public void attachTo(Launcher launcher) {
50         attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
51                 launcher::clearPendingExecutor);
52     }
53 
54     /**
55      * Attached the executor to the existence of the view
56      */
attachTo(View attachedView, boolean waitForLoadAnimation, Consumer<ViewOnDrawExecutor> onClearCallback)57     public void attachTo(View attachedView, boolean waitForLoadAnimation,
58             Consumer<ViewOnDrawExecutor> onClearCallback) {
59         mOnClearCallback = onClearCallback;
60         mAttachedView = attachedView;
61         mAttachedView.addOnAttachStateChangeListener(this);
62         if (!waitForLoadAnimation) {
63             mLoadAnimationCompleted = true;
64         }
65 
66         if (mAttachedView.isAttachedToWindow()) {
67             attachObserver();
68         }
69     }
70 
attachObserver()71     private void attachObserver() {
72         if (!mCompleted) {
73             mAttachedView.getViewTreeObserver().addOnDrawListener(this);
74         }
75     }
76 
77     @Override
execute(Runnable command)78     public void execute(Runnable command) {
79         mTasks.add(command);
80         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
81     }
82 
83     @Override
onViewAttachedToWindow(View v)84     public void onViewAttachedToWindow(View v) {
85         attachObserver();
86     }
87 
88     @Override
onViewDetachedFromWindow(View v)89     public void onViewDetachedFromWindow(View v) {}
90 
91     @Override
onDraw()92     public void onDraw() {
93         mFirstDrawCompleted = true;
94         mAttachedView.post(this);
95     }
96 
onLoadAnimationCompleted()97     public void onLoadAnimationCompleted() {
98         mLoadAnimationCompleted = true;
99         if (mAttachedView != null) {
100             mAttachedView.post(this);
101         }
102     }
103 
104     @Override
run()105     public void run() {
106         // Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called.
107         if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) {
108             runAllTasks();
109         }
110     }
111 
markCompleted()112     public void markCompleted() {
113         mTasks.clear();
114         mCompleted = true;
115         if (mAttachedView != null) {
116             mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
117             mAttachedView.removeOnAttachStateChangeListener(this);
118         }
119         if (mOnClearCallback != null) {
120             mOnClearCallback.accept(this);
121         }
122         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
123     }
124 
isCompleted()125     protected boolean isCompleted() {
126         return mCompleted;
127     }
128 
129     /**
130      * Executes all tasks immediately
131      */
132     @VisibleForTesting
runAllTasks()133     public void runAllTasks() {
134         for (final Runnable r : mTasks) {
135             r.run();
136         }
137         markCompleted();
138     }
139 }
140