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