/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui; import static android.os.IBinder.FLAG_ONEWAY; import static com.android.settingslib.utils.ThreadUtils.isMainThread; import android.annotation.MainThread; import android.os.Binder; import android.os.Binder.ProxyTransactListener; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; import android.os.SystemProperties; import android.view.Choreographer; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.util.Assert; import java.util.ArrayList; import java.util.HashSet; import java.util.Stack; import java.util.function.Supplier; /** * Utility class for methods used to dejank the UI. */ public class DejankUtils { public static final boolean STRICT_MODE_ENABLED = Build.IS_ENG || SystemProperties.getBoolean("persist.sysui.strictmode", false); private static final Choreographer sChoreographer = Choreographer.getInstance(); private static final Handler sHandler = new Handler(); private static final ArrayList sPendingRunnables = new ArrayList<>(); private static Stack sBlockingIpcs = new Stack<>(); private static boolean sTemporarilyIgnoreStrictMode; private static final HashSet sWhitelistedFrameworkClasses = new HashSet<>(); private static final Object sLock = new Object(); private static final ProxyTransactListener sProxy = new ProxyTransactListener() { @Override public Object onTransactStarted(IBinder binder, int transactionCode, int flags) { synchronized (sLock) { if ((flags & FLAG_ONEWAY) == FLAG_ONEWAY || sBlockingIpcs.empty() || !isMainThread() || sTemporarilyIgnoreStrictMode) { return null; } } try { String description = binder.getInterfaceDescriptor(); synchronized (sLock) { if (sWhitelistedFrameworkClasses.contains(description)) { return null; } } } catch (RemoteException e) { e.printStackTrace(); } StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); return null; } @Override public Object onTransactStarted(IBinder binder, int transactionCode) { return null; } @Override public void onTransactEnded(Object o) { } }; /** * Only for testing. */ private static boolean sImmediate; static { if (STRICT_MODE_ENABLED) { // Common IPCs that are ok to block the main thread. sWhitelistedFrameworkClasses.add("android.view.IWindowSession"); sWhitelistedFrameworkClasses.add("com.android.internal.policy.IKeyguardStateCallback"); sWhitelistedFrameworkClasses.add("android.os.IPowerManager"); sWhitelistedFrameworkClasses.add("com.android.internal.statusbar.IStatusBarService"); Binder.setProxyTransactListener(sProxy); StrictMode.ThreadPolicy.Builder builder = new StrictMode.ThreadPolicy.Builder() .detectCustomSlowCalls() .penaltyFlashScreen() .penaltyLog(); StrictMode.setThreadPolicy(builder.build()); } } private static final Runnable sAnimationCallbackRunnable = () -> { for (int i = 0; i < sPendingRunnables.size(); i++) { sHandler.post(sPendingRunnables.get(i)); } sPendingRunnables.clear(); }; /** * Enable blocking-binder-call {@link StrictMode} for a {@link Runnable}. * * @param runnable Target. */ @MainThread public static void detectBlockingIpcs(Runnable runnable) { if (STRICT_MODE_ENABLED && sBlockingIpcs.empty()) { synchronized (sLock) { sBlockingIpcs.push("detectBlockingIpcs"); } try { runnable.run(); } finally { synchronized (sLock) { sBlockingIpcs.pop(); } } } else { runnable.run(); } } /** * Enable blocking-binder-call {@link StrictMode}. * * @param tag A key. * @see #detectBlockingIpcs(Runnable) */ @MainThread public static void startDetectingBlockingIpcs(String tag) { if (STRICT_MODE_ENABLED) { synchronized (sLock) { sBlockingIpcs.push(tag); } } } /** * Stop IPC detection for a specific tag. * * @param tag The key. * @see #startDetectingBlockingIpcs(String) */ @MainThread public static void stopDetectingBlockingIpcs(String tag) { if (STRICT_MODE_ENABLED) { synchronized (sLock) { sBlockingIpcs.remove(tag); } } } /** * Temporarily ignore blocking binder calls for contents of this {@link Runnable}. * * @param runnable Target. */ @MainThread public static void whitelistIpcs(Runnable runnable) { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; } try { runnable.run(); } finally { synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } } } else { runnable.run(); } } /** * @see #whitelistIpcs(Runnable) */ @MainThread public static T whitelistIpcs(Supplier supplier) { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; } final T val; try { val = supplier.get(); } finally { synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } } return val; } else { return supplier.get(); } } /** * Executes {@code r} after performTraversals. Use this do to CPU heavy work for which the * timing is not critical for animation. The work is then scheduled at the same time * RenderThread is doing its thing, leading to better parallelization. * *

Needs to be called from the main thread. */ public static void postAfterTraversal(Runnable r) { if (sImmediate) { r.run(); return; } Assert.isMainThread(); sPendingRunnables.add(r); postAnimationCallback(); } /** * Removes a previously scheduled runnable. * *

Needs to be called from the main thread. */ public static void removeCallbacks(Runnable r) { Assert.isMainThread(); sPendingRunnables.remove(r); sHandler.removeCallbacks(r); } private static void postAnimationCallback() { sChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, sAnimationCallbackRunnable, null); } @VisibleForTesting public static void setImmediate(boolean immediate) { sImmediate = immediate; } }