Merge "Adding main thread binder tracing in development build." into udc-qpr-dev
This commit is contained in:
@@ -1,159 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2019 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.launcher3.uioverrides;
|
||||
|
||||
import static android.os.IBinder.FLAG_ONEWAY;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A binder proxy transaction listener for tracking non-whitelisted binder calls.
|
||||
*/
|
||||
public class DejankBinderTracker implements Binder.ProxyTransactListener {
|
||||
private static final String TAG = "DejankBinderTracker";
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>();
|
||||
static {
|
||||
// Common IPCs that are ok to block the main thread.
|
||||
sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
|
||||
sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
|
||||
}
|
||||
private static boolean sTemporarilyIgnoreTracking = false;
|
||||
|
||||
// Used by the client to limit binder tracking to specific regions
|
||||
private static boolean sTrackingAllowed = false;
|
||||
|
||||
private BiConsumer<String, Integer> mUnexpectedTransactionCallback;
|
||||
private boolean mIsTracking = false;
|
||||
|
||||
/**
|
||||
* Temporarily ignore blocking binder calls for the duration of this {@link Runnable}.
|
||||
*/
|
||||
@MainThread
|
||||
public static void whitelistIpcs(Runnable runnable) {
|
||||
sTemporarilyIgnoreTracking = true;
|
||||
runnable.run();
|
||||
sTemporarilyIgnoreTracking = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
|
||||
*/
|
||||
@MainThread
|
||||
public static <T> T whitelistIpcs(Supplier<T> supplier) {
|
||||
sTemporarilyIgnoreTracking = true;
|
||||
T value = supplier.get();
|
||||
sTemporarilyIgnoreTracking = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables binder tracking during a test.
|
||||
*/
|
||||
@MainThread
|
||||
public static void allowBinderTrackingInTests() {
|
||||
sTrackingAllowed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables binder tracking during a test.
|
||||
*/
|
||||
@MainThread
|
||||
public static void disallowBinderTrackingInTests() {
|
||||
sTrackingAllowed = false;
|
||||
}
|
||||
|
||||
public DejankBinderTracker(BiConsumer<String, Integer> unexpectedTransactionCallback) {
|
||||
mUnexpectedTransactionCallback = unexpectedTransactionCallback;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void startTracking() {
|
||||
if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
|
||||
&& !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) {
|
||||
Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception());
|
||||
return;
|
||||
}
|
||||
if (mIsTracking) {
|
||||
return;
|
||||
}
|
||||
mIsTracking = true;
|
||||
Binder.setProxyTransactListener(this);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void stopTracking() {
|
||||
if (!mIsTracking) {
|
||||
return;
|
||||
}
|
||||
mIsTracking = false;
|
||||
Binder.setProxyTransactListener(null);
|
||||
}
|
||||
|
||||
// Override the hidden Binder#onTransactStarted method
|
||||
public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
|
||||
if (!mIsTracking
|
||||
|| !sTrackingAllowed
|
||||
|| sTemporarilyIgnoreTracking
|
||||
|| (flags & FLAG_ONEWAY) == FLAG_ONEWAY
|
||||
|| !isMainThread()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String descriptor;
|
||||
try {
|
||||
descriptor = binder.getInterfaceDescriptor();
|
||||
if (sWhitelistedFrameworkClasses.contains(descriptor)) {
|
||||
return null;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
descriptor = binder.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
mUnexpectedTransactionCallback.accept(descriptor, transactionCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder binder, int transactionCode) {
|
||||
// Do nothing
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactEnded(Object session) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public static boolean isMainThread() {
|
||||
return Thread.currentThread() == Looper.getMainLooper().getThread();
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,7 @@ import com.android.launcher3.tracing.SwipeHandlerProto;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
@@ -587,7 +588,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (mWasLauncherAlreadyVisible) {
|
||||
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
|
||||
} else {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
|
||||
SafeCloseable traceToken = TraceHelper.INSTANCE.beginAsyncSection("WTS-init");
|
||||
View dragLayer = activity.getDragLayer();
|
||||
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
|
||||
boolean mHandled = false;
|
||||
@@ -599,7 +600,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
mHandled = true;
|
||||
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
traceToken.close();
|
||||
dragLayer.post(() ->
|
||||
dragLayer.getViewTreeObserver().removeOnDrawListener(this));
|
||||
if (activity != mActivity) {
|
||||
@@ -681,11 +682,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
private void initializeLauncherAnimationController() {
|
||||
buildAnimationController();
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
|
||||
TraceHelper.FLAG_IGNORE_BINDERS);
|
||||
LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
|
||||
(int) (mLauncherFrameDrawnTime - mTouchTimeMs));
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
try (SafeCloseable c = TraceHelper.INSTANCE.allowIpcs("logToggleRecents")) {
|
||||
LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
|
||||
(int) (mLauncherFrameDrawnTime - mTouchTimeMs));
|
||||
}
|
||||
|
||||
// This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
|
||||
// high-res thumbnail loader here once we are sure that we will end up in an overview state
|
||||
@@ -2039,10 +2039,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
private void setScreenshotCapturedState() {
|
||||
// If we haven't posted a draw callback, set the state immediately.
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
|
||||
TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
|
||||
TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT);
|
||||
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
private void finishCurrentTransitionToRecents() {
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Copyright (C) 2023 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.quickstep;
|
||||
|
||||
import static android.os.IBinder.FLAG_ONEWAY;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Binder.ProxyTransactListener;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Trace;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import kotlin.random.Random;
|
||||
|
||||
/**
|
||||
* A binder proxy transaction listener for tracking binder calls on main thread.
|
||||
*/
|
||||
public class BinderTracker {
|
||||
|
||||
private static final String TAG = "BinderTracker";
|
||||
|
||||
// Common IPCs that are ok to block the main thread.
|
||||
private static final Set<String> sAllowedFrameworkClasses = Set.of(
|
||||
"android.view.IWindowSession",
|
||||
"android.os.IPowerManager",
|
||||
"android.os.IServiceManager");
|
||||
|
||||
/**
|
||||
* Starts tracking binder class and returns a {@link SafeCloseable} to end tracking
|
||||
*/
|
||||
public static SafeCloseable startTracking(Consumer<BinderCallSite> callback) {
|
||||
TraceHelper current = TraceHelper.INSTANCE;
|
||||
|
||||
TraceHelperExtension helper = new TraceHelperExtension(callback);
|
||||
TraceHelper.INSTANCE = helper;
|
||||
Binder.setProxyTransactListener(helper);
|
||||
|
||||
return () -> {
|
||||
Binder.setProxyTransactListener(null);
|
||||
TraceHelper.INSTANCE = current;
|
||||
};
|
||||
}
|
||||
|
||||
private static final LinkedList<String> mMainThreadTraceStack = new LinkedList<>();
|
||||
private static final LinkedList<String> mMainThreadIgnoreIpcStack = new LinkedList<>();
|
||||
|
||||
private static class TraceHelperExtension extends TraceHelper implements ProxyTransactListener {
|
||||
|
||||
private final Consumer<BinderCallSite> mUnexpectedTransactionCallback;
|
||||
|
||||
TraceHelperExtension(Consumer<BinderCallSite> unexpectedTransactionCallback) {
|
||||
mUnexpectedTransactionCallback = unexpectedTransactionCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginSection(String sectionName) {
|
||||
if (isMainThread()) {
|
||||
mMainThreadTraceStack.add(sectionName);
|
||||
}
|
||||
super.beginSection(sectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SafeCloseable beginAsyncSection(String sectionName) {
|
||||
if (!isMainThread()) {
|
||||
return super.beginAsyncSection(sectionName);
|
||||
}
|
||||
|
||||
mMainThreadTraceStack.add(sectionName);
|
||||
int cookie = Random.Default.nextInt();
|
||||
Trace.beginAsyncSection(sectionName, cookie);
|
||||
return () -> {
|
||||
Trace.endAsyncSection(sectionName, cookie);
|
||||
mMainThreadTraceStack.remove(sectionName);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endSection() {
|
||||
super.endSection();
|
||||
if (isMainThread()) {
|
||||
mMainThreadTraceStack.pollLast();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SafeCloseable allowIpcs(String rpcName) {
|
||||
if (!isMainThread()) {
|
||||
return super.allowIpcs(rpcName);
|
||||
}
|
||||
|
||||
mMainThreadTraceStack.add(rpcName);
|
||||
mMainThreadIgnoreIpcStack.add(rpcName);
|
||||
int cookie = Random.Default.nextInt();
|
||||
Trace.beginAsyncSection(rpcName, cookie);
|
||||
return () -> {
|
||||
Trace.endAsyncSection(rpcName, cookie);
|
||||
mMainThreadTraceStack.remove(rpcName);
|
||||
mMainThreadIgnoreIpcStack.remove(rpcName);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
|
||||
if (!isMainThread() || (flags & FLAG_ONEWAY) == FLAG_ONEWAY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String ipcBypass = mMainThreadIgnoreIpcStack.peekLast();
|
||||
String descriptor;
|
||||
try {
|
||||
descriptor = binder.getInterfaceDescriptor();
|
||||
if (sAllowedFrameworkClasses.contains(descriptor)) {
|
||||
return null;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error getting IPC descriptor", e);
|
||||
descriptor = binder.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
if (ipcBypass == null) {
|
||||
mUnexpectedTransactionCallback.accept(new BinderCallSite(
|
||||
mMainThreadTraceStack.peekLast(), descriptor, transactionCode));
|
||||
} else {
|
||||
Log.d(TAG, "MainThread-IPC " + descriptor + " ignored due to " + ipcBypass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder binder, int transactionCode) {
|
||||
// Do nothing
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactEnded(Object session) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMainThread() {
|
||||
return Thread.currentThread() == Looper.getMainLooper().getThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a binder call
|
||||
*/
|
||||
public static class BinderCallSite {
|
||||
|
||||
@Nullable
|
||||
public final String activeTrace;
|
||||
public final String descriptor;
|
||||
public final int transactionCode;
|
||||
|
||||
BinderCallSite(String activeTrace, String descriptor, int transactionCode) {
|
||||
this.activeTrace = activeTrace;
|
||||
this.descriptor = descriptor;
|
||||
this.transactionCode = transactionCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.util.InstantAppResolver;
|
||||
@@ -49,4 +52,14 @@ public class InstantAppResolverImpl extends InstantAppResolver {
|
||||
ComponentName cn = info.getTargetComponent();
|
||||
return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstantApp(String packageName, int userId) {
|
||||
try {
|
||||
return ActivityThread.getPackageManager().isInstantApp(packageName, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to determine whether package is instant app " + packageName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,5 +60,10 @@ public class QuickstepProcessInitializer extends MainProcessInitializer {
|
||||
// Elevate GPU priority for Quickstep and Remote animations.
|
||||
ThreadedRenderer.setContextPriority(
|
||||
ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
|
||||
|
||||
if (BuildConfig.IS_STUDIO_BUILD) {
|
||||
BinderTracker.startTracking(call -> Log.e("BinderCall",
|
||||
call.descriptor + " called on mainthread under " + call.activeTrace));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,11 +393,12 @@ public interface TaskShortcutFactory {
|
||||
@Override
|
||||
public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
|
||||
TaskIdAttributeContainer taskContainer) {
|
||||
return InstantAppResolver.newInstance(activity).isInstantApp(activity,
|
||||
taskContainer.getTask().getTopComponent().getPackageName()) ?
|
||||
Collections.singletonList(new SystemShortcut.Install(activity,
|
||||
taskContainer.getItemInfo(), taskContainer.getTaskView())) :
|
||||
null;
|
||||
Task t = taskContainer.getTask();
|
||||
return InstantAppResolver.newInstance(activity).isInstantApp(
|
||||
t.getTopComponent().getPackageName(), t.getKey().userId)
|
||||
? Collections.singletonList(new SystemShortcut.Install(activity,
|
||||
taskContainer.getItemInfo(), taskContainer.getTaskView()))
|
||||
: null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.annotation.Nullable;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
|
||||
@@ -51,7 +52,8 @@ public final class TaskUtils {
|
||||
* TODO: remove this once we switch to getting the icon and label from IconCache.
|
||||
*/
|
||||
public static CharSequence getTitle(Context context, Task task) {
|
||||
return getTitle(context, task.key.userId, task.getTopComponent().getPackageName());
|
||||
return TraceHelper.allowIpcs("TaskUtils.getTitle", () ->
|
||||
getTitle(context, task.key.userId, task.getTopComponent().getPackageName()));
|
||||
}
|
||||
|
||||
public static CharSequence getTitle(
|
||||
|
||||
@@ -102,6 +102,7 @@ import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.LockedUserState;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
|
||||
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
|
||||
@@ -700,8 +701,7 @@ public class TouchInteractionService extends Service
|
||||
return;
|
||||
}
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
|
||||
TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
|
||||
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
|
||||
|
||||
final int action = event.getActionMasked();
|
||||
// Note this will create a new consumer every mouse click, as after ACTION_UP from the click
|
||||
@@ -797,7 +797,7 @@ public class TouchInteractionService extends Service
|
||||
if (cleanUpConsumer) {
|
||||
reset();
|
||||
}
|
||||
TraceHelper.INSTANCE.endFlagsOverride(traceToken);
|
||||
traceToken.close();
|
||||
ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
|
||||
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
||||
import static com.android.launcher3.Utilities.squaredHypot;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
|
||||
import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
|
||||
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
|
||||
|
||||
@@ -229,8 +228,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
// Until we detect the gesture, handle events as we receive them
|
||||
mInputEventReceiver.setBatchingEnabled(false);
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(DOWN_EVT,
|
||||
FLAG_CHECK_FOR_RACE_CONDITIONS);
|
||||
TraceHelper.INSTANCE.beginSection(DOWN_EVT);
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
mDownPos.set(ev.getX(), ev.getY());
|
||||
mLastPos.set(mDownPos);
|
||||
@@ -241,7 +239,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
startTouchTrackingForWindowAnimation(ev.getEventTime());
|
||||
}
|
||||
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
break;
|
||||
}
|
||||
case ACTION_POINTER_DOWN: {
|
||||
@@ -417,8 +415,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
* the animation can still be running.
|
||||
*/
|
||||
private void finishTouchTracking(MotionEvent ev) {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(UP_EVT,
|
||||
FLAG_CHECK_FOR_RACE_CONDITIONS);
|
||||
TraceHelper.INSTANCE.beginSection(UP_EVT);
|
||||
|
||||
if (mPassedWindowMoveSlop && mInteractionHandler != null) {
|
||||
if (ev.getActionMasked() == ACTION_CANCEL) {
|
||||
@@ -455,7 +452,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
onInteractionGestureFinished();
|
||||
}
|
||||
cleanupAfterGesture();
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
private void cleanupAfterGesture() {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.quickstep.util;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
/**
|
||||
* Utility class to test and check binder calls during development.
|
||||
*/
|
||||
public class BinderTracker {
|
||||
|
||||
private static final String TAG = "BinderTracker";
|
||||
|
||||
public static void start() {
|
||||
if (!FeatureFlags.IS_STUDIO_BUILD) {
|
||||
Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
Binder.setProxyTransactListener(new Tracker());
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (!FeatureFlags.IS_STUDIO_BUILD) {
|
||||
Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
|
||||
return;
|
||||
}
|
||||
Binder.setProxyTransactListener(null);
|
||||
}
|
||||
|
||||
private static class Tracker implements Binder.ProxyTransactListener {
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder iBinder, int code) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Log.e(TAG, "Binder call on ui thread", new Exception());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactEnded(Object session) { }
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
||||
mContext = context;
|
||||
mSizeStrategy = sizeStrategy;
|
||||
|
||||
// TODO(b/187074722): Don't create this per-TaskViewSimulator
|
||||
mOrientationState = TraceHelper.allowIpcs("",
|
||||
mOrientationState = TraceHelper.allowIpcs("TaskViewSimulator.init",
|
||||
() -> new RecentsOrientedState(context, sizeStrategy, i -> { }));
|
||||
mOrientationState.setGestureActive(true);
|
||||
mCurrentFullscreenParams = new FullscreenDrawParams(context);
|
||||
|
||||
@@ -32,6 +32,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
|
||||
import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
|
||||
import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
@@ -89,6 +90,7 @@ import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.TransformingTouchDelegate;
|
||||
import com.android.launcher3.util.ViewPool.Reusable;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
@@ -1632,8 +1634,8 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
if (taskContainer == null) {
|
||||
continue;
|
||||
}
|
||||
for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
|
||||
taskContainer)) {
|
||||
for (SystemShortcut s : TraceHelper.allowIpcs(
|
||||
"TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) {
|
||||
info.addAction(s.createAccessibilityAction(context));
|
||||
}
|
||||
}
|
||||
@@ -1670,7 +1672,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
if (taskContainer == null) {
|
||||
continue;
|
||||
}
|
||||
for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
|
||||
for (SystemShortcut s : getEnabledShortcuts(this,
|
||||
taskContainer)) {
|
||||
if (s.hasHandlerForAction(action)) {
|
||||
s.onClick(this);
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.ui.TaplTestsLauncher3;
|
||||
import com.android.launcher3.util.RaceConditionReproducer;
|
||||
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -45,18 +44,6 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
|
||||
startTestActivity(2);
|
||||
}
|
||||
|
||||
private void runTest(String... eventSequence) {
|
||||
final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(eventSequence);
|
||||
|
||||
// Destroy Launcher activity.
|
||||
closeLauncherActivity();
|
||||
|
||||
// The test action.
|
||||
eventProcessor.startIteration();
|
||||
mLauncher.goHome();
|
||||
eventProcessor.finishIteration();
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
@NavigationModeSwitch
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2016 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/options_menu_icon_size"
|
||||
android:height="@dimen/options_menu_icon_size"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?android:attr/textColorPrimary">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,12.71l2.14,2.58l3-3.87L18,16.57H6L9,12.71z M5,5h6V3H5C3.9,3,3,3.9,3,5v6h2V5z M19,19h-6v2h6c1.1,0,2-0.9,2-2v-6h-2V19z
|
||||
M5,19v-6H3v6c0,1.1,0.9,2,2,2h6v-2H5z M19,5v6h2V5c0-1.1-0.9-2-2-2h-6v2H19z M16,9c0.55,0,1-0.45,1-1s-0.45-1-1-1
|
||||
c-0.55,0-1,0.45-1,1S15.45,9,16,9z"/>
|
||||
</vector>
|
||||
@@ -102,8 +102,6 @@
|
||||
|
||||
<!-- Default packages -->
|
||||
<string name="wallpaper_picker_package" translatable="false"></string>
|
||||
<string name="custom_activity_picker" translatable="false">
|
||||
com.android.customization.picker.CustomizationPickerActivity</string>
|
||||
<string name="local_colors_extraction_class" translatable="false"></string>
|
||||
<string name="search_session_manager_class" translatable="false"></string>
|
||||
|
||||
|
||||
@@ -250,8 +250,6 @@
|
||||
|
||||
<!-- Strings for the customization mode -->
|
||||
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
|
||||
<string name="wallpaper_button_text">Wallpapers</string>
|
||||
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
|
||||
<string name="styles_wallpaper_button_text">Wallpaper & style</string>
|
||||
<!-- Text for edit home screen button [CHAR LIMIT=30]-->
|
||||
<string name="edit_home_screen">Edit Home Screen</string>
|
||||
|
||||
@@ -442,8 +442,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
|
||||
DISPLAY_ALL_APPS_TRACE_COOKIE);
|
||||
}
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
|
||||
TraceHelper.FLAG_UI_EVENT);
|
||||
TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
|
||||
if (DEBUG_STRICT_MODE) {
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
@@ -577,7 +576,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
LauncherOverlayPlugin.class, false /* allowedMultiple */);
|
||||
|
||||
mRotationHelper.initialize();
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
|
||||
mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
|
||||
() -> getStateManager().goToState(NORMAL));
|
||||
@@ -1075,15 +1074,14 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_START_EVT,
|
||||
TraceHelper.FLAG_UI_EVENT);
|
||||
TraceHelper.INSTANCE.beginSection(ON_START_EVT);
|
||||
super.onStart();
|
||||
if (!mDeferOverlayCallbacks) {
|
||||
mOverlayManager.onActivityStarted(this);
|
||||
}
|
||||
|
||||
mAppWidgetHolder.setActivityStarted(true);
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1254,8 +1252,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT,
|
||||
TraceHelper.FLAG_UI_EVENT);
|
||||
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
|
||||
super.onResume();
|
||||
|
||||
if (mDeferOverlayCallbacks) {
|
||||
@@ -1265,7 +1262,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
}
|
||||
|
||||
DragView.removeAllViews(this);
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1653,7 +1650,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
if (Utilities.isRunningInTestHarness()) {
|
||||
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
|
||||
}
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
|
||||
TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
|
||||
super.onNewIntent(intent);
|
||||
|
||||
boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
|
||||
@@ -1700,7 +1697,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
showAllAppsWorkTabFromIntent(alreadyOnHome);
|
||||
}
|
||||
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
|
||||
@@ -2299,7 +2296,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
* Implementation of the method from LauncherModel.Callbacks.
|
||||
*/
|
||||
public void startBinding() {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("startBinding");
|
||||
TraceHelper.INSTANCE.beginSection("startBinding");
|
||||
// Floating panels (except the full widget sheet) are associated with individual icons. If
|
||||
// we are starting a fresh bind, close all such panels as all the icons are about
|
||||
// to go away.
|
||||
@@ -2317,7 +2314,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
if (mHotseat != null) {
|
||||
mHotseat.resetLayout(getDeviceProfile().isVerticalBarLayout());
|
||||
}
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2570,7 +2567,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
return view;
|
||||
}
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
|
||||
TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
|
||||
|
||||
try {
|
||||
final LauncherAppWidgetProviderInfo appWidgetInfo;
|
||||
@@ -2700,7 +2697,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
}
|
||||
prepareAppWidget(view, item);
|
||||
} finally {
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
return view;
|
||||
@@ -2793,7 +2790,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
* Implementation of the method from LauncherModel.Callbacks.
|
||||
*/
|
||||
public void finishBindingItems(IntSet pagesBoundFirst) {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
|
||||
TraceHelper.INSTANCE.beginSection("finishBindingItems");
|
||||
mWorkspace.restoreInstanceStateForRemainingPages();
|
||||
|
||||
setWorkspaceLoading(false);
|
||||
@@ -2818,7 +2815,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
|
||||
getViewCache().setCacheSize(R.layout.folder_page, 2);
|
||||
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
|
||||
mWorkspace.removeExtraEmptyScreen(true);
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public class LoaderTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
|
||||
TraceHelper.INSTANCE.beginSection(TAG);
|
||||
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
|
||||
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
|
||||
List<ShortcutInfo> allShortcuts = new ArrayList<>();
|
||||
@@ -325,7 +325,7 @@ public class LoaderTask implements Runnable {
|
||||
memoryLogger.printLogs();
|
||||
throw e;
|
||||
}
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
public synchronized void stopLocked() {
|
||||
|
||||
@@ -42,14 +42,7 @@ public class InstantAppResolver implements ResourceBasedOverride {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isInstantApp(Context context, String packageName) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
return isInstantApp(packageManager.getPackageInfo(packageName, 0).applicationInfo);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e("InstantAppResolver", "Failed to determine whether package is instant app "
|
||||
+ packageName, e);
|
||||
}
|
||||
public boolean isInstantApp(String packageName, int userId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,13 +164,6 @@ public class PackageManagerHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Intent getStyleWallpapersIntent(Context context) {
|
||||
return new Intent(Intent.ACTION_SET_WALLPAPER).setComponent(
|
||||
new ComponentName(context.getString(R.string.wallpaper_picker_package),
|
||||
context.getString(R.string.custom_activity_picker)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the details activity for {@code info}
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,8 @@ import androidx.annotation.MainThread;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import kotlin.random.Random;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link Trace} to allow better testing.
|
||||
*
|
||||
@@ -36,54 +38,53 @@ public class TraceHelper {
|
||||
// Temporarily ignore blocking binder calls for this trace.
|
||||
public static final int FLAG_IGNORE_BINDERS = 1 << 1;
|
||||
|
||||
public static final int FLAG_CHECK_FOR_RACE_CONDITIONS = 1 << 2;
|
||||
|
||||
public static final int FLAG_UI_EVENT =
|
||||
FLAG_ALLOW_BINDER_TRACKING | FLAG_CHECK_FOR_RACE_CONDITIONS;
|
||||
|
||||
/**
|
||||
* Static instance of Trace helper, overridden in tests.
|
||||
*/
|
||||
public static TraceHelper INSTANCE = new TraceHelper();
|
||||
|
||||
/**
|
||||
* @return a token to pass into {@link #endSection(Object)}.
|
||||
* @see Trace#beginSection(String)
|
||||
*/
|
||||
public Object beginSection(String sectionName) {
|
||||
return beginSection(sectionName, 0);
|
||||
}
|
||||
|
||||
public Object beginSection(String sectionName, int flags) {
|
||||
public void beginSection(String sectionName) {
|
||||
Trace.beginSection(sectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token the token returned from {@link #beginSection(String, int)}
|
||||
* @see Trace#endSection()
|
||||
*/
|
||||
public void endSection(Object token) {
|
||||
public void endSection() {
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #beginSection} but doesn't add a trace section.
|
||||
* @see Trace#beginAsyncSection(String, int)
|
||||
* @return a SafeCloseable that can be used to end the session
|
||||
*/
|
||||
public Object beginFlagsOverride(int flags) {
|
||||
return null;
|
||||
public SafeCloseable beginAsyncSection(String sectionName) {
|
||||
int cookie = Random.Default.nextInt();
|
||||
Trace.beginAsyncSection(sectionName, cookie);
|
||||
return () -> Trace.endAsyncSection(sectionName, cookie);
|
||||
}
|
||||
|
||||
public void endFlagsOverride(Object token) { }
|
||||
/**
|
||||
* Returns a SafeCloseable to temporarily ignore blocking binder calls.
|
||||
*/
|
||||
public SafeCloseable allowIpcs(String rpcName) {
|
||||
int cookie = Random.Default.nextInt();
|
||||
Trace.beginAsyncSection(rpcName, cookie);
|
||||
return () -> Trace.endAsyncSection(rpcName, cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
|
||||
*
|
||||
* Note, new features should be designed to not rely on mainThread RPCs.
|
||||
*/
|
||||
@MainThread
|
||||
public static <T> T allowIpcs(String rpcName, Supplier<T> supplier) {
|
||||
Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS);
|
||||
try {
|
||||
try (SafeCloseable c = INSTANCE.allowIpcs(rpcName)) {
|
||||
return supplier.get();
|
||||
} finally {
|
||||
INSTANCE.endSection(traceToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ import com.android.launcher3.popup.ArrowPopup;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.widget.picker.WidgetsFullSheet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -190,14 +189,9 @@ public class OptionsPopupView extends ArrowPopup<Launcher>
|
||||
*/
|
||||
public static ArrayList<OptionItem> getOptions(Launcher launcher) {
|
||||
ArrayList<OptionItem> options = new ArrayList<>();
|
||||
boolean styleWallpaperExists = styleWallpapersExists(launcher);
|
||||
int resString = styleWallpaperExists
|
||||
? R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
|
||||
int resDrawable = styleWallpaperExists
|
||||
? R.drawable.ic_palette : R.drawable.ic_wallpaper;
|
||||
options.add(new OptionItem(launcher,
|
||||
resString,
|
||||
resDrawable,
|
||||
R.string.styles_wallpaper_button_text,
|
||||
R.drawable.ic_palette,
|
||||
IGNORE,
|
||||
OptionsPopupView::startWallpaperPicker));
|
||||
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
|
||||
@@ -274,12 +268,8 @@ public class OptionsPopupView extends ArrowPopup<Launcher>
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
.putExtra(EXTRA_WALLPAPER_OFFSET,
|
||||
launcher.getWorkspace().getWallpaperOffsetForCenterPage())
|
||||
.putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher");
|
||||
if (!styleWallpapersExists(launcher)) {
|
||||
intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
|
||||
} else {
|
||||
intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
|
||||
}
|
||||
.putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher")
|
||||
.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
|
||||
String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
|
||||
if (!TextUtils.isEmpty(pickerPackage)) {
|
||||
intent.setPackage(pickerPackage);
|
||||
@@ -322,9 +312,4 @@ public class OptionsPopupView extends ArrowPopup<Launcher>
|
||||
this.clickListener = clickListener;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean styleWallpapersExists(Context context) {
|
||||
return context.getPackageManager().resolveActivity(
|
||||
PackageManagerHelper.getStyleWallpapersIntent(context), 0) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,500 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.launcher3.util;
|
||||
|
||||
import static com.android.launcher3.util.Executors.createAndStartNewLooper;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Event processor for reliably reproducing multithreaded apps race conditions in tests.
|
||||
*
|
||||
* The app notifies us about “events” that happen in its threads. The race condition test runs the
|
||||
* test action multiple times (aka iterations), trying to generate all possible permutations of
|
||||
* these events. It keeps a set of all seen event sequences and steers the execution towards
|
||||
* executing events in previously unseen order. It does it by postponing execution of threads that
|
||||
* would lead to an already seen sequence.
|
||||
*
|
||||
* If an event A occurs before event B in the sequence, this is how execution order looks like:
|
||||
* Events: ... A ... B ...
|
||||
* Events and instructions, guaranteed order:
|
||||
* (instructions executed prior to A) A ... B (instructions executed after B)
|
||||
*
|
||||
* Each iteration has 3 parts (phases).
|
||||
* Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen
|
||||
* continuations. Reproducing this sequence by pausing threads that would lead to other sequences.
|
||||
* Phase 2. Trying to generate previously unseen continuation of the sequence from Phase 1. We need
|
||||
* one new event after that sequence. All threads leading to seen continuations will be postponed
|
||||
* for some short period of time. The phase ends once the new event is registered, or after the
|
||||
* period of time ends (in which case we declare that the sequence can’t have new continuations).
|
||||
* Phase 3. Releasing all threads and letting the test iteration run till its end.
|
||||
*
|
||||
* The iterations end when all seen paths have been declared “uncontinuable”.
|
||||
*
|
||||
* When we register event XXX:enter, we hold all other events until we register XXX:exit.
|
||||
*/
|
||||
public class RaceConditionReproducer {
|
||||
private static final String TAG = "RaceConditionReproducer";
|
||||
|
||||
private static final boolean ENTER = true;
|
||||
private static final boolean EXIT = false;
|
||||
private static final String ENTER_POSTFIX = "enter";
|
||||
private static final String EXIT_POSTFIX = "exit";
|
||||
|
||||
private static final long SHORT_TIMEOUT_MS = 2000;
|
||||
private static final long LONG_TIMEOUT_MS = 60000;
|
||||
// Handler used to resume postponed events.
|
||||
private static final Handler POSTPONED_EVENT_RESUME_HANDLER =
|
||||
new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
|
||||
|
||||
public static String enterExitEvt(String eventName, boolean isEnter) {
|
||||
return eventName + ":" + (isEnter ? ENTER_POSTFIX : EXIT_POSTFIX);
|
||||
}
|
||||
|
||||
public static String enterEvt(String eventName) {
|
||||
return enterExitEvt(eventName, ENTER);
|
||||
}
|
||||
|
||||
public static String exitEvt(String eventName) {
|
||||
return enterExitEvt(eventName, EXIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event in a particular sequence of events. A node in the prefix tree of all seen event
|
||||
* sequences.
|
||||
*/
|
||||
private class EventNode {
|
||||
// Events that were seen just after this event.
|
||||
private final Map<String, EventNode> mNextEvents = new HashMap<>();
|
||||
// Whether we believe that further iterations will not be able to add more events to
|
||||
// mNextEvents.
|
||||
private boolean mStoppedAddingChildren = true;
|
||||
|
||||
private void debugDump(StringBuilder sb, int indent, String name) {
|
||||
for (int i = 0; i < indent; ++i) sb.append('.');
|
||||
sb.append(!mStoppedAddingChildren ? "+" : "-");
|
||||
sb.append(" : ");
|
||||
sb.append(name);
|
||||
if (mLastRegisteredEvent == this) sb.append(" <");
|
||||
sb.append('\n');
|
||||
|
||||
for (String key : mNextEvents.keySet()) {
|
||||
mNextEvents.get(key).debugDump(sb, indent + 2, key);
|
||||
}
|
||||
}
|
||||
|
||||
/** Number of leaves in the subtree with this node as a root. */
|
||||
private int numberOfLeafNodes() {
|
||||
if (mNextEvents.isEmpty()) return 1;
|
||||
|
||||
int leaves = 0;
|
||||
for (String event : mNextEvents.keySet()) {
|
||||
leaves += mNextEvents.get(event).numberOfLeafNodes();
|
||||
}
|
||||
return leaves;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we believe that further iterations will not be able add nodes to the subtree with
|
||||
* this node as a root.
|
||||
*/
|
||||
private boolean stoppedAddingChildrenToTree() {
|
||||
if (!mStoppedAddingChildren) return false;
|
||||
|
||||
for (String event : mNextEvents.keySet()) {
|
||||
if (!mNextEvents.get(event).stoppedAddingChildrenToTree()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the subtree with this node as a root, tries finding a node where we may have a
|
||||
* chance to add new children.
|
||||
* If succeeds, returns true and fills 'path' with the sequence of events to that node;
|
||||
* otherwise returns false.
|
||||
*/
|
||||
private boolean populatePathToGrowthPoint(List<String> path) {
|
||||
for (String event : mNextEvents.keySet()) {
|
||||
if (mNextEvents.get(event).populatePathToGrowthPoint(path)) {
|
||||
path.add(0, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!mStoppedAddingChildren) {
|
||||
// Mark that we have finished adding children. It will remain true if no new
|
||||
// children are added, or will be set to false upon adding a new child.
|
||||
mStoppedAddingChildren = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Starting point of all event sequences; the root of the prefix tree representation all
|
||||
// sequences generated by test iterations. A test iteration can add nodes int it.
|
||||
private EventNode mRoot = new EventNode();
|
||||
// During a test iteration, the last event that was registered.
|
||||
private EventNode mLastRegisteredEvent;
|
||||
// Length of the current sequence of registered events for the current test iteration.
|
||||
private int mRegisteredEventCount = 0;
|
||||
// During the first part of a test iteration, we go to a specific node under mRoot by
|
||||
// 'playing back' mSequenceToFollow. During this part, all events that don't belong to this
|
||||
// sequence get postponed.
|
||||
private List<String> mSequenceToFollow = new ArrayList<>();
|
||||
// Collection of events that got postponed, with corresponding wait objects used to let them go.
|
||||
private Map<String, Semaphore> mPostponedEvents = new HashMap<>();
|
||||
// Callback to run by POSTPONED_EVENT_RESUME_HANDLER, used to let go of all currently
|
||||
// postponed events.
|
||||
private Runnable mResumeAllEventsCallback;
|
||||
// String representation of the sequence of events registered so far for the current test
|
||||
// iteration. After registering any event, we output it to the log. The last output before
|
||||
// the test failure can be later played back to reliable reproduce the exact sequence of
|
||||
// events that broke the test.
|
||||
// Format: EV1|EV2|...\EVN
|
||||
private StringBuilder mCurrentSequence;
|
||||
// When not null, we are in a repro mode. We run only one test iteration, and are trying to
|
||||
// reproduce the event sequence represented by this string. The format is same as for
|
||||
// mCurrentSequence.
|
||||
private final String mReproString;
|
||||
|
||||
/* Constructor for a normal test. */
|
||||
public RaceConditionReproducer() {
|
||||
mReproString = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for reliably reproducing a race condition failure. The developer should find in
|
||||
* the log the latest "Repro sequence:" record and locally modify the test by passing that
|
||||
* string to the constructor. Running the test will have only one iteration that will reliably
|
||||
* "play back" that sequence.
|
||||
*/
|
||||
public RaceConditionReproducer(String reproString) {
|
||||
mReproString = reproString;
|
||||
}
|
||||
|
||||
public RaceConditionReproducer(String... reproSequence) {
|
||||
this(String.join("|", reproSequence));
|
||||
}
|
||||
|
||||
public synchronized String getCurrentSequenceString() {
|
||||
return mCurrentSequence.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new test iteration. Events reported via RaceConditionTracker.onEvent before this
|
||||
* call will be ignored.
|
||||
*/
|
||||
public synchronized void startIteration() {
|
||||
mLastRegisteredEvent = mRoot;
|
||||
mRegisteredEventCount = 0;
|
||||
mCurrentSequence = new StringBuilder();
|
||||
Log.d(TAG, "Repro sequence: " + mCurrentSequence);
|
||||
mSequenceToFollow = mReproString != null ?
|
||||
parseReproString(mReproString) : generateSequenceToFollowLocked();
|
||||
Log.e(TAG, "---- Start of iteration; state:\n" + dumpStateLocked());
|
||||
checkIfCompletedSequenceToFollowLocked();
|
||||
|
||||
TraceHelperForTest.setRaceConditionReproducer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a new test iteration. Events reported via RaceConditionTracker.onEvent after this call
|
||||
* will be ignored.
|
||||
* Returns whether we need more iterations.
|
||||
*/
|
||||
public synchronized boolean finishIteration() {
|
||||
TraceHelperForTest.setRaceConditionReproducer(null);
|
||||
|
||||
runResumeAllEventsCallbackLocked();
|
||||
assertTrue("Non-empty postponed events", mPostponedEvents.isEmpty());
|
||||
assertTrue("Last registered event is :enter", lastEventAsEnter() == null);
|
||||
|
||||
// No events came after mLastRegisteredEvent. It doesn't make sense to come to it again
|
||||
// because we won't see new continuations.
|
||||
mLastRegisteredEvent.mStoppedAddingChildren = true;
|
||||
Log.e(TAG, "---- End of iteration; state:\n" + dumpStateLocked());
|
||||
if (mReproString != null) {
|
||||
assertTrue("Repro mode: failed to reproduce the sequence",
|
||||
mCurrentSequence.toString().startsWith(mReproString));
|
||||
}
|
||||
// If we are in a repro mode, we need only one iteration. Otherwise, continue if the tree
|
||||
// has prospective growth points.
|
||||
return mReproString == null && !mRoot.stoppedAddingChildrenToTree();
|
||||
}
|
||||
|
||||
private static List<String> parseReproString(String reproString) {
|
||||
return Arrays.asList(reproString.split("\\|"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the app issues an event.
|
||||
*/
|
||||
public void onEvent(String event) {
|
||||
final Semaphore waitObject = tryRegisterEvent(event);
|
||||
if (waitObject != null) {
|
||||
waitUntilCanRegister(event, waitObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last event was not an XXX:enter, or this event is a matching XXX:exit.
|
||||
*/
|
||||
private boolean canRegisterEventNowLocked(String event) {
|
||||
final String lastEventAsEnter = lastEventAsEnter();
|
||||
final String thisEventAsExit = eventAsExit(event);
|
||||
|
||||
if (lastEventAsEnter != null) {
|
||||
if (!lastEventAsEnter.equals(thisEventAsExit)) {
|
||||
assertTrue("YYY:exit after XXX:enter", thisEventAsExit == null);
|
||||
// Last event was :enter, but this event is not :exit.
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Previous event was not :enter.
|
||||
assertTrue(":exit after a non-enter event", thisEventAsExit == null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an event issued by the app and returns null or decides that the event must be
|
||||
* postponed, and returns an object to wait on.
|
||||
*/
|
||||
private synchronized Semaphore tryRegisterEvent(String event) {
|
||||
Log.d(TAG, "Event issued by the app: " + event);
|
||||
|
||||
if (!canRegisterEventNowLocked(event)) {
|
||||
return createWaitObjectForPostponedEventLocked(event);
|
||||
}
|
||||
|
||||
if (mRegisteredEventCount < mSequenceToFollow.size()) {
|
||||
// We are in the first part of the iteration. We only register events that follow the
|
||||
// mSequenceToFollow and postponing all other events.
|
||||
if (event.equals(mSequenceToFollow.get(mRegisteredEventCount))) {
|
||||
// The event is the next one expected in the sequence. Register it.
|
||||
registerEventLocked(event);
|
||||
|
||||
// If there are postponed events that could continue the sequence, register them.
|
||||
while (mRegisteredEventCount < mSequenceToFollow.size() &&
|
||||
mPostponedEvents.containsKey(
|
||||
mSequenceToFollow.get(mRegisteredEventCount))) {
|
||||
registerPostponedEventLocked(mSequenceToFollow.get(mRegisteredEventCount));
|
||||
}
|
||||
|
||||
// Perhaps we just completed the required sequence...
|
||||
checkIfCompletedSequenceToFollowLocked();
|
||||
} else {
|
||||
// The event is not the next one in the sequence. Postpone it.
|
||||
return createWaitObjectForPostponedEventLocked(event);
|
||||
}
|
||||
} else if (mRegisteredEventCount == mSequenceToFollow.size()) {
|
||||
// The second phase of the iteration. We have just registered the whole
|
||||
// mSequenceToFollow, and want to add previously not seen continuations for the last
|
||||
// node in the sequence aka 'growth point'.
|
||||
if (!mLastRegisteredEvent.mNextEvents.containsKey(event) || mReproString != null) {
|
||||
// The event was never seen as a continuation for the current node.
|
||||
// Or we are in repro mode, in which case we are not in business of generating
|
||||
// new sequences after we've played back the required sequence.
|
||||
// Register it immediately.
|
||||
registerEventLocked(event);
|
||||
} else {
|
||||
// The event was seen as a continuation for the current node. Postpone it, hoping
|
||||
// that a new event will come from other threads.
|
||||
return createWaitObjectForPostponedEventLocked(event);
|
||||
}
|
||||
} else {
|
||||
// The third phase of the iteration. We are past the growth point and register
|
||||
// everything that comes.
|
||||
registerEventLocked(event);
|
||||
// Register events that may have been postponed while waiting for an :exit event
|
||||
// during the third phase. We don't do this if just registered event is :enter.
|
||||
if (eventAsEnter(event) == null && mRegisteredEventCount > mSequenceToFollow.size()) {
|
||||
registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Called when there are chances that we just have registered the whole mSequenceToFollow. */
|
||||
private void checkIfCompletedSequenceToFollowLocked() {
|
||||
if (mRegisteredEventCount == mSequenceToFollow.size()) {
|
||||
// We just entered the second phase of the iteration. We have just registered the
|
||||
// whole mSequenceToFollow, and want to add previously not seen continuations for the
|
||||
// last node in the sequence aka 'growth point'. All seen continuations will be
|
||||
// postponed for SHORT_TIMEOUT_MS. At the end of this time period, we'll let them go.
|
||||
scheduleResumeAllEventsLocked();
|
||||
|
||||
// Among the events that were postponed during the first stage, there may be an event
|
||||
// that wasn't seen after the current. If so, register it immediately because this
|
||||
// creates a new sequence.
|
||||
final Set<String> keys = new HashSet<>(mPostponedEvents.keySet());
|
||||
keys.removeAll(mLastRegisteredEvent.mNextEvents.keySet());
|
||||
if (!keys.isEmpty()) {
|
||||
registerPostponedEventLocked(keys.iterator().next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Semaphore createWaitObjectForPostponedEventLocked(String event) {
|
||||
final Semaphore waitObject = new Semaphore(0);
|
||||
assertTrue("Event already postponed: " + event, !mPostponedEvents.containsKey(event));
|
||||
mPostponedEvents.put(event, waitObject);
|
||||
return waitObject;
|
||||
}
|
||||
|
||||
private void waitUntilCanRegister(String event, Semaphore waitObject) {
|
||||
try {
|
||||
assertTrue("Never registered event: " + event,
|
||||
waitObject.tryAcquire(LONG_TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} catch (InterruptedException e) {
|
||||
fail("Wait was interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
/** Schedules resuming all postponed events after SHORT_TIMEOUT_MS */
|
||||
private void scheduleResumeAllEventsLocked() {
|
||||
assertTrue(mResumeAllEventsCallback == null);
|
||||
mResumeAllEventsCallback = this::allEventsResumeCallback;
|
||||
POSTPONED_EVENT_RESUME_HANDLER.postDelayed(mResumeAllEventsCallback, SHORT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private synchronized void allEventsResumeCallback() {
|
||||
assertTrue("In callback, but callback is not set", mResumeAllEventsCallback != null);
|
||||
mResumeAllEventsCallback = null;
|
||||
registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
|
||||
}
|
||||
|
||||
private void registerPostponedEventsLocked(Collection<String> events) {
|
||||
for (String event : events) {
|
||||
registerPostponedEventLocked(event);
|
||||
if (eventAsEnter(event) != null) {
|
||||
// Once :enter is registered, switch to waiting for :exit to come. Won't register
|
||||
// other postponed events.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPostponedEventLocked(String event) {
|
||||
mPostponedEvents.remove(event).release();
|
||||
registerEventLocked(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the last registered event was XXX:enter, returns XXX, otherwise, null.
|
||||
*/
|
||||
private String lastEventAsEnter() {
|
||||
return eventAsEnter(mCurrentSequence.substring(mCurrentSequence.lastIndexOf("|") + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the event is XXX:postfix, returns XXX, otherwise, null.
|
||||
*/
|
||||
private static String prefixFromPostfixedEvent(String event, String postfix) {
|
||||
final int columnPos = event.indexOf(':');
|
||||
if (columnPos != -1 && postfix.equals(event.substring(columnPos + 1))) {
|
||||
return event.substring(0, columnPos);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the event is XXX:enter, returns XXX, otherwise, null.
|
||||
*/
|
||||
private static String eventAsEnter(String event) {
|
||||
return prefixFromPostfixedEvent(event, ENTER_POSTFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the event is XXX:exit, returns XXX, otherwise, null.
|
||||
*/
|
||||
private static String eventAsExit(String event) {
|
||||
return prefixFromPostfixedEvent(event, EXIT_POSTFIX);
|
||||
}
|
||||
|
||||
private void registerEventLocked(String event) {
|
||||
assertTrue(canRegisterEventNowLocked(event));
|
||||
|
||||
Log.d(TAG, "Actually registering event: " + event);
|
||||
EventNode next = mLastRegisteredEvent.mNextEvents.get(event);
|
||||
if (next == null) {
|
||||
// This event wasn't seen after mLastRegisteredEvent.
|
||||
next = new EventNode();
|
||||
mLastRegisteredEvent.mNextEvents.put(event, next);
|
||||
// The fact that we've added a new event after the previous one means that the
|
||||
// previous event is still a growth point, unless this event is :exit, which means
|
||||
// that the previous event is :enter.
|
||||
mLastRegisteredEvent.mStoppedAddingChildren = eventAsExit(event) != null;
|
||||
}
|
||||
|
||||
mLastRegisteredEvent = next;
|
||||
mRegisteredEventCount++;
|
||||
|
||||
if (mCurrentSequence.length() > 0) mCurrentSequence.append("|");
|
||||
mCurrentSequence.append(event);
|
||||
Log.d(TAG, "Repro sequence: " + mCurrentSequence);
|
||||
}
|
||||
|
||||
private void runResumeAllEventsCallbackLocked() {
|
||||
if (mResumeAllEventsCallback != null) {
|
||||
POSTPONED_EVENT_RESUME_HANDLER.removeCallbacks(mResumeAllEventsCallback);
|
||||
mResumeAllEventsCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence dumpStateLocked() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Sequence to follow: ");
|
||||
for (String event : mSequenceToFollow) sb.append(" " + event);
|
||||
sb.append(".\n");
|
||||
sb.append("Registered event count: " + mRegisteredEventCount);
|
||||
|
||||
sb.append("\nPostponed events: ");
|
||||
for (String event : mPostponedEvents.keySet()) sb.append(" " + event);
|
||||
sb.append(".");
|
||||
|
||||
sb.append("\nNodes: \n");
|
||||
mRoot.debugDump(sb, 0, "");
|
||||
return sb;
|
||||
}
|
||||
|
||||
public int numberOfLeafNodes() {
|
||||
return mRoot.numberOfLeafNodes();
|
||||
}
|
||||
|
||||
private List<String> generateSequenceToFollowLocked() {
|
||||
ArrayList<String> sequence = new ArrayList<>();
|
||||
mRoot.populatePathToGrowthPoint(sequence);
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.launcher3.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RaceConditionReproducerTest {
|
||||
private final static String SOME_VALID_SEQUENCE_3_3 = "B1|A1|A2|B2|A3|B3";
|
||||
|
||||
private static int factorial(int n) {
|
||||
int res = 1;
|
||||
for (int i = 2; i <= n; ++i) res *= i;
|
||||
return res;
|
||||
}
|
||||
|
||||
RaceConditionReproducer eventProcessor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
eventProcessor = new RaceConditionReproducer();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TraceHelperForTest.cleanup();
|
||||
}
|
||||
|
||||
private void run3_3_TestAction() throws InterruptedException {
|
||||
Thread tb = new Thread(() -> {
|
||||
eventProcessor.onEvent("B1");
|
||||
eventProcessor.onEvent("B2");
|
||||
eventProcessor.onEvent("B3");
|
||||
});
|
||||
tb.start();
|
||||
|
||||
eventProcessor.onEvent("A1");
|
||||
eventProcessor.onEvent("A2");
|
||||
eventProcessor.onEvent("A3");
|
||||
|
||||
tb.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // The test is too long for continuous testing.
|
||||
// 2 threads, 3 events each.
|
||||
public void test3_3() throws Exception {
|
||||
boolean sawTheValidSequence = false;
|
||||
|
||||
for (; ; ) {
|
||||
eventProcessor.startIteration();
|
||||
run3_3_TestAction();
|
||||
final boolean needMoreIterations = eventProcessor.finishIteration();
|
||||
|
||||
sawTheValidSequence = sawTheValidSequence ||
|
||||
SOME_VALID_SEQUENCE_3_3.equals(eventProcessor.getCurrentSequenceString());
|
||||
|
||||
if (!needMoreIterations) break;
|
||||
}
|
||||
|
||||
assertEquals("Wrong number of leaf nodes",
|
||||
factorial(3 + 3) / (factorial(3) * factorial(3)),
|
||||
eventProcessor.numberOfLeafNodes());
|
||||
assertTrue(sawTheValidSequence);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // The test is too long for continuous testing.
|
||||
// 2 threads, 3 events, including enter-exit pairs each.
|
||||
public void test3_3_enter_exit() throws Exception {
|
||||
boolean sawTheValidSequence = false;
|
||||
|
||||
for (; ; ) {
|
||||
eventProcessor.startIteration();
|
||||
Thread tb = new Thread(() -> {
|
||||
eventProcessor.onEvent("B1:enter");
|
||||
eventProcessor.onEvent("B1:exit");
|
||||
eventProcessor.onEvent("B2");
|
||||
eventProcessor.onEvent("B3:enter");
|
||||
eventProcessor.onEvent("B3:exit");
|
||||
});
|
||||
tb.start();
|
||||
|
||||
eventProcessor.onEvent("A1");
|
||||
eventProcessor.onEvent("A2:enter");
|
||||
eventProcessor.onEvent("A2:exit");
|
||||
eventProcessor.onEvent("A3:enter");
|
||||
eventProcessor.onEvent("A3:exit");
|
||||
|
||||
tb.join();
|
||||
final boolean needMoreIterations = eventProcessor.finishIteration();
|
||||
|
||||
sawTheValidSequence = sawTheValidSequence ||
|
||||
"B1:enter|B1:exit|A1|A2:enter|A2:exit|B2|A3:enter|A3:exit|B3:enter|B3:exit".
|
||||
equals(eventProcessor.getCurrentSequenceString());
|
||||
|
||||
if (!needMoreIterations) break;
|
||||
}
|
||||
|
||||
assertEquals("Wrong number of leaf nodes",
|
||||
factorial(3 + 3) / (factorial(3) * factorial(3)),
|
||||
eventProcessor.numberOfLeafNodes());
|
||||
assertTrue(sawTheValidSequence);
|
||||
}
|
||||
|
||||
@Test
|
||||
// 2 threads, 3 events each; reproducing a particular event sequence.
|
||||
public void test3_3_ReproMode() throws Exception {
|
||||
eventProcessor = new RaceConditionReproducer(SOME_VALID_SEQUENCE_3_3);
|
||||
eventProcessor.startIteration();
|
||||
run3_3_TestAction();
|
||||
assertTrue(!eventProcessor.finishIteration());
|
||||
assertEquals(SOME_VALID_SEQUENCE_3_3, eventProcessor.getCurrentSequenceString());
|
||||
|
||||
assertEquals("Wrong number of leaf nodes", 1, eventProcessor.numberOfLeafNodes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // The test is too long for continuous testing.
|
||||
// 2 threads with 2 events; 1 thread with 1 event.
|
||||
public void test2_1_2() throws Exception {
|
||||
for (; ; ) {
|
||||
eventProcessor.startIteration();
|
||||
Thread tb = new Thread(() -> {
|
||||
eventProcessor.onEvent("B1");
|
||||
eventProcessor.onEvent("B2");
|
||||
});
|
||||
tb.start();
|
||||
|
||||
Thread tc = new Thread(() -> {
|
||||
eventProcessor.onEvent("C1");
|
||||
});
|
||||
tc.start();
|
||||
|
||||
eventProcessor.onEvent("A1");
|
||||
eventProcessor.onEvent("A2");
|
||||
|
||||
tb.join();
|
||||
tc.join();
|
||||
|
||||
if (!eventProcessor.finishIteration()) break;
|
||||
}
|
||||
|
||||
assertEquals("Wrong number of leaf nodes",
|
||||
factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
|
||||
eventProcessor.numberOfLeafNodes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // The test is too long for continuous testing.
|
||||
// 2 threads with 2 events; 1 thread with 1 event. Includes enter-exit pairs.
|
||||
public void test2_1_2_enter_exit() throws Exception {
|
||||
for (; ; ) {
|
||||
eventProcessor.startIteration();
|
||||
Thread tb = new Thread(() -> {
|
||||
eventProcessor.onEvent("B1:enter");
|
||||
eventProcessor.onEvent("B1:exit");
|
||||
eventProcessor.onEvent("B2:enter");
|
||||
eventProcessor.onEvent("B2:exit");
|
||||
});
|
||||
tb.start();
|
||||
|
||||
Thread tc = new Thread(() -> {
|
||||
eventProcessor.onEvent("C1:enter");
|
||||
eventProcessor.onEvent("C1:exit");
|
||||
});
|
||||
tc.start();
|
||||
|
||||
eventProcessor.onEvent("A1:enter");
|
||||
eventProcessor.onEvent("A1:exit");
|
||||
eventProcessor.onEvent("A2:enter");
|
||||
eventProcessor.onEvent("A2:exit");
|
||||
|
||||
tb.join();
|
||||
tc.join();
|
||||
|
||||
if (!eventProcessor.finishIteration()) break;
|
||||
}
|
||||
|
||||
assertEquals("Wrong number of leaf nodes",
|
||||
factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
|
||||
eventProcessor.numberOfLeafNodes());
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2019 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.launcher3.util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
public class TraceHelperForTest extends TraceHelper {
|
||||
|
||||
private static final TraceHelperForTest INSTANCE_FOR_TEST = new TraceHelperForTest();
|
||||
|
||||
private final ThreadLocal<LinkedList<TraceInfo>> mStack =
|
||||
ThreadLocal.withInitial(LinkedList::new);
|
||||
|
||||
private RaceConditionReproducer mRaceConditionReproducer;
|
||||
private IntConsumer mFlagsChangeListener;
|
||||
|
||||
public static void setRaceConditionReproducer(RaceConditionReproducer reproducer) {
|
||||
TraceHelper.INSTANCE = INSTANCE_FOR_TEST;
|
||||
INSTANCE_FOR_TEST.mRaceConditionReproducer = reproducer;
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
INSTANCE_FOR_TEST.mRaceConditionReproducer = null;
|
||||
INSTANCE_FOR_TEST.mFlagsChangeListener = null;
|
||||
}
|
||||
|
||||
public static void setFlagsChangeListener(IntConsumer listener) {
|
||||
TraceHelper.INSTANCE = INSTANCE_FOR_TEST;
|
||||
INSTANCE_FOR_TEST.mFlagsChangeListener = listener;
|
||||
}
|
||||
|
||||
private TraceHelperForTest() { }
|
||||
|
||||
@Override
|
||||
public Object beginSection(String sectionName, int flags) {
|
||||
LinkedList<TraceInfo> stack = mStack.get();
|
||||
TraceInfo info = new TraceInfo(sectionName, flags);
|
||||
stack.add(info);
|
||||
|
||||
if ((flags & TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS) != 0
|
||||
&& mRaceConditionReproducer != null) {
|
||||
mRaceConditionReproducer.onEvent(RaceConditionReproducer.enterEvt(sectionName));
|
||||
}
|
||||
updateBinderTracking(stack);
|
||||
|
||||
super.beginSection(sectionName, flags);
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endSection(Object token) {
|
||||
LinkedList<TraceInfo> stack = mStack.get();
|
||||
if (stack.size() == 0) {
|
||||
new Throwable().printStackTrace();
|
||||
}
|
||||
TraceInfo info = (TraceInfo) token;
|
||||
stack.remove(info);
|
||||
if ((info.flags & TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS) != 0
|
||||
&& mRaceConditionReproducer != null) {
|
||||
mRaceConditionReproducer.onEvent(RaceConditionReproducer.exitEvt(info.sectionName));
|
||||
}
|
||||
updateBinderTracking(stack);
|
||||
|
||||
super.endSection(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beginFlagsOverride(int flags) {
|
||||
LinkedList<TraceInfo> stack = mStack.get();
|
||||
TraceInfo info = new TraceInfo(null, flags);
|
||||
stack.add(info);
|
||||
updateBinderTracking(stack);
|
||||
super.beginFlagsOverride(flags);
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endFlagsOverride(Object token) {
|
||||
super.endFlagsOverride(token);
|
||||
LinkedList<TraceInfo> stack = mStack.get();
|
||||
TraceInfo info = (TraceInfo) token;
|
||||
stack.remove(info);
|
||||
updateBinderTracking(stack);
|
||||
}
|
||||
|
||||
private void updateBinderTracking(LinkedList<TraceInfo> stack) {
|
||||
if (mFlagsChangeListener != null) {
|
||||
mFlagsChangeListener.accept(stack.stream()
|
||||
.mapToInt(info -> info.flags).reduce(0, (a, b) -> a | b));
|
||||
}
|
||||
}
|
||||
|
||||
private static class TraceInfo {
|
||||
public final String sectionName;
|
||||
public final int flags;
|
||||
|
||||
TraceInfo(String sectionName, int flags) {
|
||||
this.sectionName = sectionName;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user