diff --git a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java deleted file mode 100644 index d8aa235823..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java +++ /dev/null @@ -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 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 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 whitelistIpcs(Supplier 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 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(); - } -} diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 2b921884d2..bf4896d2cb 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -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, 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, } 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, 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, 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() { diff --git a/quickstep/src/com/android/quickstep/BinderTracker.java b/quickstep/src/com/android/quickstep/BinderTracker.java new file mode 100644 index 0000000000..a876cd868b --- /dev/null +++ b/quickstep/src/com/android/quickstep/BinderTracker.java @@ -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 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 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 mMainThreadTraceStack = new LinkedList<>(); + private static final LinkedList mMainThreadIgnoreIpcStack = new LinkedList<>(); + + private static class TraceHelperExtension extends TraceHelper implements ProxyTransactListener { + + private final Consumer mUnexpectedTransactionCallback; + + TraceHelperExtension(Consumer 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; + } + } +} diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java index 7638541023..529213c5de 100644 --- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java +++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java @@ -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; + } + } } diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java index 5f589bfa50..128b0451c3 100644 --- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java +++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java @@ -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)); + } } } diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index 813523888d..810c028341 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -393,11 +393,12 @@ public interface TaskShortcutFactory { @Override public List 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; } }; diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java index 67360c4cc3..80a449bf5b 100644 --- a/quickstep/src/com/android/quickstep/TaskUtils.java +++ b/quickstep/src/com/android/quickstep/TaskUtils.java @@ -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( diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 2a1cc7032c..af57172cdb 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -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(); } diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 5b27f9bde1..10c6316ac1 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -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() { diff --git a/quickstep/src/com/android/quickstep/util/BinderTracker.java b/quickstep/src/com/android/quickstep/util/BinderTracker.java deleted file mode 100644 index cb04e5b015..0000000000 --- a/quickstep/src/com/android/quickstep/util/BinderTracker.java +++ /dev/null @@ -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) { } - } -} diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index f8893bd9dd..7f035a2b5a 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -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); diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index f93d6d2aac..839c3fd2c5 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -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); diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index df5303f4f4..5127190cec 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -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 diff --git a/res/drawable/ic_wallpaper.xml b/res/drawable/ic_wallpaper.xml deleted file mode 100644 index 9543f88192..0000000000 --- a/res/drawable/ic_wallpaper.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - diff --git a/res/values/config.xml b/res/values/config.xml index 5a6698b80e..83f840d0b2 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -102,8 +102,6 @@ - - com.android.customization.picker.CustomizationPickerActivity diff --git a/res/values/strings.xml b/res/values/strings.xml index c2eb3735af..1b46b4dba8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -250,8 +250,6 @@ - Wallpapers - Wallpaper & style Edit Home Screen diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 03b1e54560..3ba8a1d462 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -442,8 +442,7 @@ public class Launcher extends StatefulActivity 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 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 @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 @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 } DragView.removeAllViews(this); - TraceHelper.INSTANCE.endSection(traceToken); + TraceHelper.INSTANCE.endSection(); } @Override @@ -1653,7 +1650,7 @@ public class Launcher extends StatefulActivity 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 showAllAppsWorkTabFromIntent(alreadyOnHome); } - TraceHelper.INSTANCE.endSection(traceToken); + TraceHelper.INSTANCE.endSection(); } protected void toggleAllAppsFromIntent(boolean alreadyOnHome) { @@ -2299,7 +2296,7 @@ public class Launcher extends StatefulActivity * 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 if (mHotseat != null) { mHotseat.resetLayout(getDeviceProfile().isVerticalBarLayout()); } - TraceHelper.INSTANCE.endSection(traceToken); + TraceHelper.INSTANCE.endSection(); } @Override @@ -2570,7 +2567,7 @@ public class Launcher extends StatefulActivity 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 } prepareAppWidget(view, item); } finally { - TraceHelper.INSTANCE.endSection(traceToken); + TraceHelper.INSTANCE.endSection(); } return view; @@ -2793,7 +2790,7 @@ public class Launcher extends StatefulActivity * 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 mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows); getViewCache().setCacheSize(R.layout.folder_page, 2); - TraceHelper.INSTANCE.endSection(traceToken); + TraceHelper.INSTANCE.endSection(); mWorkspace.removeExtraEmptyScreen(true); } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index d2a81745a1..73a06b6237 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -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 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() { diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java index 6f706d2be1..bdb5e775ad 100644 --- a/src/com/android/launcher3/util/InstantAppResolver.java +++ b/src/com/android/launcher3/util/InstantAppResolver.java @@ -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; } } diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 1d6bc253ec..91203a7f9b 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -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} */ diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java index c23df77db1..d5056eea15 100644 --- a/src/com/android/launcher3/util/TraceHelper.java +++ b/src/com/android/launcher3/util/TraceHelper.java @@ -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 allowIpcs(String rpcName, Supplier supplier) { - Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS); - try { + try (SafeCloseable c = INSTANCE.allowIpcs(rpcName)) { return supplier.get(); - } finally { - INSTANCE.endSection(traceToken); } } } diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index aebf752941..55febc7352 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -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 */ public static ArrayList getOptions(Launcher launcher) { ArrayList 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 .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 this.clickListener = clickListener; } } - - private static boolean styleWallpapersExists(Context context) { - return context.getPackageManager().resolveActivity( - PackageManagerHelper.getStyleWallpapersIntent(context), 0) != null; - } } diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java deleted file mode 100644 index ed2ec7b40a..0000000000 --- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java +++ /dev/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 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 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 mSequenceToFollow = new ArrayList<>(); - // Collection of events that got postponed, with corresponding wait objects used to let them go. - private Map 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 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 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 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 generateSequenceToFollowLocked() { - ArrayList sequence = new ArrayList<>(); - mRoot.populatePathToGrowthPoint(sequence); - return sequence; - } -} diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java deleted file mode 100644 index 59f21734f4..0000000000 --- a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java +++ /dev/null @@ -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()); - } -} diff --git a/tests/src/com/android/launcher3/util/TraceHelperForTest.java b/tests/src/com/android/launcher3/util/TraceHelperForTest.java deleted file mode 100644 index f1c8a67f9a..0000000000 --- a/tests/src/com/android/launcher3/util/TraceHelperForTest.java +++ /dev/null @@ -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> 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 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 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 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 stack = mStack.get(); - TraceInfo info = (TraceInfo) token; - stack.remove(info); - updateBinderTracking(stack); - } - - private void updateBinderTracking(LinkedList 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; - } - } -}