• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.systemui;
18 
19 import static android.os.IBinder.FLAG_ONEWAY;
20 
21 import static com.android.settingslib.utils.ThreadUtils.isMainThread;
22 
23 import android.annotation.MainThread;
24 import android.os.Binder;
25 import android.os.Binder.ProxyTransactListener;
26 import android.os.Build;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.StrictMode;
31 import android.os.SystemProperties;
32 import android.os.Trace;
33 import android.view.Choreographer;
34 import android.view.View;
35 import android.view.ViewRootImpl;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.systemui.util.Assert;
39 
40 import java.util.ArrayList;
41 import java.util.HashSet;
42 import java.util.Random;
43 import java.util.Stack;
44 import java.util.function.Supplier;
45 
46 /**
47  * Utility class for methods used to dejank the UI.
48  */
49 public class DejankUtils {
50     private static final String TRACK_NAME = "DejankUtils";
51 
52     public static final boolean STRICT_MODE_ENABLED = Build.IS_ENG
53             || SystemProperties.getBoolean("persist.sysui.strictmode", false);
54     private static final Choreographer sChoreographer = Choreographer.getInstance();
55     private static final Handler sHandler = new Handler();
56     private static final ArrayList<Runnable> sPendingRunnables = new ArrayList<>();
57     private static final Random sRandom = new Random();
58     private static Stack<String> sBlockingIpcs = new Stack<>();
59     private static boolean sTemporarilyIgnoreStrictMode;
60     private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>();
61     private static final Object sLock = new Object();
62     private static final ProxyTransactListener sProxy = new ProxyTransactListener() {
63         @Override
64         public Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
65             synchronized (sLock) {
66                 if ((flags & FLAG_ONEWAY) == FLAG_ONEWAY || sBlockingIpcs.empty()
67                         || !isMainThread() || sTemporarilyIgnoreStrictMode) {
68                     return null;
69                 }
70             }
71 
72             try {
73                 String description = binder.getInterfaceDescriptor();
74                 synchronized (sLock) {
75                     if (sWhitelistedFrameworkClasses.contains(description)) {
76                         return null;
77                     }
78                 }
79             } catch (RemoteException e) {
80                 e.printStackTrace();
81             }
82 
83             StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek());
84             return null;
85         }
86 
87         @Override
88         public Object onTransactStarted(IBinder binder, int transactionCode) {
89             return null;
90         }
91 
92         @Override
93         public void onTransactEnded(Object o) {
94 
95         }
96     };
97 
98     /**
99      * Only for testing.
100      */
101     private static boolean sImmediate;
102 
103     static {
104         if (STRICT_MODE_ENABLED) {
105             // Common IPCs that are ok to block the main thread.
106             sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
107             sWhitelistedFrameworkClasses.add("com.android.internal.policy.IKeyguardStateCallback");
108             sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
109             sWhitelistedFrameworkClasses.add("com.android.internal.statusbar.IStatusBarService");
110 
111             Binder.setProxyTransactListener(sProxy);
112             StrictMode.ThreadPolicy.Builder builder = new StrictMode.ThreadPolicy.Builder()
113                     .detectCustomSlowCalls()
114                     .penaltyFlashScreen()
115                     .penaltyLog();
builder.build()116             StrictMode.setThreadPolicy(builder.build());
117         }
118     }
119 
120     private static final Runnable sAnimationCallbackRunnable = () -> {
121         for (int i = 0; i < sPendingRunnables.size(); i++) {
122             sHandler.post(sPendingRunnables.get(i));
123         }
124         sPendingRunnables.clear();
125     };
126 
127     /**
128      * Enable blocking-binder-call {@link StrictMode} for a {@link Runnable}.
129      *
130      * @param runnable Target.
131      */
132     @MainThread
detectBlockingIpcs(Runnable runnable)133     public static void detectBlockingIpcs(Runnable runnable) {
134         if (STRICT_MODE_ENABLED && sBlockingIpcs.empty()) {
135             synchronized (sLock) {
136                 sBlockingIpcs.push("detectBlockingIpcs");
137             }
138             try {
139                 runnable.run();
140             } finally {
141                 synchronized (sLock) {
142                     sBlockingIpcs.pop();
143                 }
144             }
145         } else {
146             runnable.run();
147         }
148     }
149 
150     /**
151      * Enable blocking-binder-call {@link StrictMode}.
152      *
153      * @param tag A key.
154      * @see #detectBlockingIpcs(Runnable)
155      */
156     @MainThread
startDetectingBlockingIpcs(String tag)157     public static void startDetectingBlockingIpcs(String tag) {
158         if (STRICT_MODE_ENABLED) {
159             synchronized (sLock) {
160                 sBlockingIpcs.push(tag);
161             }
162         }
163     }
164 
165     /**
166      * Stop IPC detection for a specific tag.
167      *
168      * @param tag The key.
169      * @see #startDetectingBlockingIpcs(String)
170      */
171     @MainThread
stopDetectingBlockingIpcs(String tag)172     public static void stopDetectingBlockingIpcs(String tag) {
173         if (STRICT_MODE_ENABLED) {
174             synchronized (sLock) {
175                 sBlockingIpcs.remove(tag);
176             }
177         }
178     }
179 
180     /**
181      * Temporarily ignore blocking binder calls for contents of this {@link Runnable}.
182      *
183      * @param runnable Target.
184      */
185     @MainThread
whitelistIpcs(Runnable runnable)186     public static void whitelistIpcs(Runnable runnable) {
187         if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) {
188             synchronized (sLock) {
189                 sTemporarilyIgnoreStrictMode = true;
190             }
191             try {
192                 runnable.run();
193             } finally {
194                 synchronized (sLock) {
195                     sTemporarilyIgnoreStrictMode = false;
196                 }
197             }
198         } else {
199             runnable.run();
200         }
201     }
202 
203     /**
204      * @see #whitelistIpcs(Runnable)
205      */
206     @MainThread
whitelistIpcs(Supplier<T> supplier)207     public static <T> T whitelistIpcs(Supplier<T> supplier) {
208         if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) {
209             synchronized (sLock) {
210                 sTemporarilyIgnoreStrictMode = true;
211             }
212             final T val;
213             try {
214                 val = supplier.get();
215             } finally {
216                 synchronized (sLock) {
217                     sTemporarilyIgnoreStrictMode = false;
218                 }
219             }
220             return val;
221         } else {
222             return supplier.get();
223         }
224     }
225 
226     /**
227      * Executes {@code r} after performTraversals. Use this do to CPU heavy work for which the
228      * timing is not critical for animation. The work is then scheduled at the same time
229      * RenderThread is doing its thing, leading to better parallelization.
230      *
231      * <p>Needs to be called from the main thread.
232      */
postAfterTraversal(Runnable r)233     public static void postAfterTraversal(Runnable r) {
234         if (sImmediate) {
235             r.run();
236             return;
237         }
238         Assert.isMainThread();
239         sPendingRunnables.add(r);
240         postAnimationCallback();
241     }
242 
243     /**
244      * Removes a previously scheduled runnable.
245      *
246      * <p>Needs to be called from the main thread.
247      */
removeCallbacks(Runnable r)248     public static void removeCallbacks(Runnable r) {
249         Assert.isMainThread();
250         sPendingRunnables.remove(r);
251         sHandler.removeCallbacks(r);
252     }
253 
postAnimationCallback()254     private static void postAnimationCallback() {
255         sChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, sAnimationCallbackRunnable,
256                 null);
257     }
258 
259     @VisibleForTesting
setImmediate(boolean immediate)260     public static void setImmediate(boolean immediate) {
261         sImmediate = immediate;
262     }
263 
264     /**
265      * Calls notifyRendererOfExpensiveFrame on the ViewRootImpl after performing null checks.
266      */
notifyRendererOfExpensiveFrame(View view, String reason)267     public static void notifyRendererOfExpensiveFrame(View view, String reason) {
268         if (view == null) return;
269         notifyRendererOfExpensiveFrame(view.getViewRootImpl(), reason);
270     }
271 
272     /**
273      * Calls notifyRendererOfExpensiveFrame on the ViewRootImpl after performing null checks.
274      */
notifyRendererOfExpensiveFrame(ViewRootImpl viewRoot, String reason)275     public static void notifyRendererOfExpensiveFrame(ViewRootImpl viewRoot, String reason) {
276         if (viewRoot == null) return;
277         if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
278             int cookie = sRandom.nextInt();
279             Trace.asyncTraceForTrackBegin(
280                     Trace.TRACE_TAG_APP,
281                     TRACK_NAME,
282                     "notifyRendererOfExpensiveFrame (" + reason + ")",
283                     cookie);
284             DejankUtils.postAfterTraversal(
285                     () -> Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, cookie));
286         }
287         viewRoot.notifyRendererOfExpensiveFrame();
288     }
289 }
290