This can lead to ANRs and deadlocks, given that we might wait for an unreasonable amount of time. Test: atest Test: manual Fixes: 148634425 Change-Id: Iad7f47b18613d2aa3a5590d4c7c9556bbf545735
258 lines
7.9 KiB
Java
258 lines
7.9 KiB
Java
/*
|
|
* 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<Runnable> sPendingRunnables = new ArrayList<>();
|
|
private static Stack<String> sBlockingIpcs = new Stack<>();
|
|
private static boolean sTemporarilyIgnoreStrictMode;
|
|
private static final HashSet<String> 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> T whitelistIpcs(Supplier<T> 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.
|
|
*
|
|
* <p>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.
|
|
*
|
|
* <p>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;
|
|
}
|
|
}
|