diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index c46926ec00..7c22726874 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -94,6 +94,7 @@ import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer; import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.ActiveGestureLog.CompoundString; import com.android.quickstep.util.ProtoTracer; import com.android.quickstep.util.ProxyScreenStatusProvider; import com.android.quickstep.util.SplitScreenBounds; @@ -126,6 +127,9 @@ import java.util.function.Function; public class TouchInteractionService extends Service implements ProtoTraceable { + private static final String SUBSTRING_PREFIX = "; "; + private static final String NEWLINE_PREFIX = "\n\t\t\t-> "; + private static final String TAG = "TouchInteractionService"; private static final boolean BUBBLES_HOME_GESTURE_ENABLED = @@ -619,8 +623,6 @@ public class TouchInteractionService extends Service mConsumer.onConsumerAboutToBeSwitched(); mGestureState = newGestureState; mConsumer = newConsumer(prevGestureState, mGestureState, event); - - ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName()); mUncheckedConsumer = mConsumer; } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode() && mDeviceState.canTriggerAssistantAction(event)) { @@ -628,8 +630,7 @@ public class TouchInteractionService extends Service // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we // should not interrupt it. QuickSwitch assumes that interruption can only // happen if the next gesture is also quick switch. - mUncheckedConsumer = tryCreateAssistantInputConsumer( - InputConsumer.NO_OP, mGestureState, event); + mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event); } else if (mDeviceState.canTriggerOneHandedAction(event)) { // Consume gesture event for triggering one handed feature. mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState, @@ -676,17 +677,31 @@ public class TouchInteractionService extends Service ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate(); } - private InputConsumer tryCreateAssistantInputConsumer(InputConsumer base, + private InputConsumer tryCreateAssistantInputConsumer( GestureState gestureState, MotionEvent motionEvent) { - return mDeviceState.isGestureBlockedTask(gestureState.getRunningTask()) - ? base - : new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat, - mDeviceState, motionEvent); + return tryCreateAssistantInputConsumer( + InputConsumer.NO_OP, gestureState, motionEvent, CompoundString.NO_OP); + } + + private InputConsumer tryCreateAssistantInputConsumer( + InputConsumer base, + GestureState gestureState, + MotionEvent motionEvent, + CompoundString reasonString) { + if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) { + reasonString.append(SUBSTRING_PREFIX) + .append("is gesture-blocked task, using base input consumer"); + return base; + } else { + reasonString.append(SUBSTRING_PREFIX).append("using AssistantInputConsumer"); + return new AssistantInputConsumer( + this, gestureState, base, mInputMonitorCompat, mDeviceState, motionEvent); + } } public GestureState createGestureState(GestureState previousGestureState) { GestureState gestureState = new GestureState(mOverviewComponentObserver, - ActiveGestureLog.INSTANCE.generateAndSetLogId()); + ActiveGestureLog.INSTANCE.incrementLogId()); if (mTaskAnimationManager.isRecentsAnimationRunning()) { gestureState.updateRunningTask(previousGestureState.getRunningTask()); gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId()); @@ -699,50 +714,88 @@ public class TouchInteractionService extends Service return gestureState; } - private InputConsumer newConsumer(GestureState previousGestureState, - GestureState newGestureState, MotionEvent event) { + private InputConsumer newConsumer( + GestureState previousGestureState, GestureState newGestureState, MotionEvent event) { AnimatedFloat progressProxy = mSwipeUpProxyProvider.apply(mGestureState); if (progressProxy != null) { - return new ProgressDelegateInputConsumer(this, mTaskAnimationManager, - mGestureState, mInputMonitorCompat, progressProxy); + InputConsumer consumer = new ProgressDelegateInputConsumer( + this, mTaskAnimationManager, mGestureState, mInputMonitorCompat, progressProxy); + + logInputConsumerSelectionReason(consumer, newCompoundString( + "mSwipeUpProxyProvider has been set, using ProgressDelegateInputConsumer")); + + return consumer; } boolean canStartSystemGesture = mDeviceState.canStartSystemGesture(); if (!mDeviceState.isUserUnlocked()) { + CompoundString reasonString = newCompoundString("device locked"); + InputConsumer consumer; if (canStartSystemGesture) { // This handles apps launched in direct boot mode (e.g. dialer) as well as apps // launched while device is locked even after exiting direct boot mode (e.g. camera). - return createDeviceLockedInputConsumer(newGestureState); + consumer = createDeviceLockedInputConsumer( + newGestureState, reasonString.append(SUBSTRING_PREFIX) + .append("can start system gesture")); } else { - return getDefaultInputConsumer(); + consumer = getDefaultInputConsumer( + reasonString.append(SUBSTRING_PREFIX) + .append("cannot start system gesture")); } + logInputConsumerSelectionReason(consumer, reasonString); + return consumer; } + CompoundString reasonString; + InputConsumer base; // When there is an existing recents animation running, bypass systemState check as this is // a followup gesture and the first gesture started in a valid system state. - InputConsumer base = canStartSystemGesture - || previousGestureState.isRecentsAnimationRunning() - ? newBaseConsumer(previousGestureState, newGestureState, event) - : getDefaultInputConsumer(); + if (canStartSystemGesture || previousGestureState.isRecentsAnimationRunning()) { + reasonString = newCompoundString(canStartSystemGesture + ? "can start system gesture" : "recents animation was running") + .append(", trying to use base consumer"); + base = newBaseConsumer(previousGestureState, newGestureState, event, reasonString); + } else { + reasonString = newCompoundString( + "cannot start system gesture and recents animation was not running") + .append(", trying to use default input consumer"); + base = getDefaultInputConsumer(reasonString); + } if (mDeviceState.isGesturalNavMode()) { handleOrientationSetup(base); } if (mDeviceState.isFullyGesturalNavMode()) { + String reasonPrefix = "device is in gesture navigation mode"; if (mDeviceState.canTriggerAssistantAction(event)) { - base = tryCreateAssistantInputConsumer(base, newGestureState, event); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("gesture can trigger the assistant") + .append(", trying to use assistant input consumer"); + base = tryCreateAssistantInputConsumer(base, newGestureState, event, reasonString); } // If Taskbar is present, we listen for long press to unstash it. TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext(); if (tac != null) { + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("TaskbarActivityContext != null, using TaskbarStashInputConsumer"); base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac); } if (mDeviceState.isBubblesExpanded()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("bubbles expanded"); if (BUBBLES_HOME_GESTURE_ENABLED) { + reasonString.append(SUBSTRING_PREFIX) + .append("bubbles can handle the home gesture") + .append(", trying to use default input consumer"); // Bubbles can handle home gesture itself. - base = getDefaultInputConsumer(); + base = getDefaultInputConsumer(reasonString); } else { // If Bubbles is expanded, use the overlay input consumer, which will close // Bubbles instead of going all the way home when a swipe up is detected. @@ -750,6 +803,9 @@ public class TouchInteractionService extends Service // expanded in the back. Make sure swipe up is not passed to bubbles in this // case. if (!mDeviceState.isNotificationPanelExpanded()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("using SysUiOverlayInputConsumer"); base = new SysUiOverlayInputConsumer( getBaseContext(), mDeviceState, mInputMonitorCompat); } @@ -757,6 +813,9 @@ public class TouchInteractionService extends Service } if (mDeviceState.isSystemUiDialogShowing()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("system dialog is showing, using SysUiOverlayInputConsumer"); base = new SysUiOverlayInputConsumer( getBaseContext(), mDeviceState, mInputMonitorCompat); } @@ -764,44 +823,91 @@ public class TouchInteractionService extends Service if (mDeviceState.isScreenPinningActive()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("screen pinning is active, using ScreenPinnedInputConsumer"); // Note: we only allow accessibility to wrap this, and it replaces the previous // base input consumer (which should be NO_OP anyway since topTaskLocked == true). base = new ScreenPinnedInputConsumer(this, newGestureState); } if (mDeviceState.canTriggerOneHandedAction(event)) { - base = new OneHandedModeInputConsumer(this, mDeviceState, base, - mInputMonitorCompat); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("gesture can trigger one handed mode") + .append(", using OneHandedModeInputConsumer"); + base = new OneHandedModeInputConsumer( + this, mDeviceState, base, mInputMonitorCompat); } if (mDeviceState.isAccessibilityMenuAvailable()) { - base = new AccessibilityInputConsumer(this, mDeviceState, base, - mInputMonitorCompat); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("accessibility menu is available") + .append(", using AccessibilityInputConsumer"); + base = new AccessibilityInputConsumer( + this, mDeviceState, base, mInputMonitorCompat); } } else { + String reasonPrefix = "device is not in gesture navigation mode"; if (mDeviceState.isScreenPinningActive()) { - base = getDefaultInputConsumer(); + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("screen pinning is active, trying to use default input consumer"); + base = getDefaultInputConsumer(reasonString); } if (mDeviceState.canTriggerOneHandedAction(event)) { - base = new OneHandedModeInputConsumer(this, mDeviceState, base, - mInputMonitorCompat); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("gesture can trigger one handed mode") + .append(", using OneHandedModeInputConsumer"); + base = new OneHandedModeInputConsumer( + this, mDeviceState, base, mInputMonitorCompat); } } + logInputConsumerSelectionReason(base, reasonString); return base; } + private CompoundString newCompoundString(String substring) { + return new CompoundString(NEWLINE_PREFIX).append(substring); + } + + private void logInputConsumerSelectionReason( + InputConsumer consumer, CompoundString reasonString) { + if (!FeatureFlags.ENABLE_INPUT_CONSUMER_REASON_LOGGING.get()) { + ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + consumer.getName()); + return; + } + ActiveGestureLog.INSTANCE.addLog(new CompoundString("setInputConsumer: ") + .append(consumer.getName()) + .append(". reason(s):") + .append(reasonString)); + } + private void handleOrientationSetup(InputConsumer baseInputConsumer) { baseInputConsumer.notifyOrientationSetup(); } - private InputConsumer newBaseConsumer(GestureState previousGestureState, - GestureState gestureState, MotionEvent event) { + private InputConsumer newBaseConsumer( + GestureState previousGestureState, + GestureState gestureState, + MotionEvent event, + CompoundString reasonString) { if (mDeviceState.isKeyguardShowingOccluded()) { // This handles apps showing over the lockscreen (e.g. camera) - return createDeviceLockedInputConsumer(gestureState); + return createDeviceLockedInputConsumer( + gestureState, + reasonString.append(SUBSTRING_PREFIX) + .append("keyguard is showing occluded") + .append(", trying to use device locked input consumer")); } + reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded"); // Use overview input consumer for sharesheets on top of home. boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted() && gestureState.getRunningTask() != null @@ -815,23 +921,46 @@ public class TouchInteractionService extends Service forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask(); } + boolean previousGestureAnimatedToLauncher = + previousGestureState.isRunningAnimationToLauncher(); + // with shell-transitions, home is resumed during recents animation, so + // explicitly check against recents animation too. + boolean launcherResumedThroughShellTransition = + gestureState.getActivityInterface().isResumed() + && !previousGestureState.isRecentsAnimationRunning(); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode()) { return createOverviewInputConsumer( - previousGestureState, gestureState, event, forceOverviewInputConsumer); + previousGestureState, + gestureState, + event, + forceOverviewInputConsumer, + reasonString.append(SUBSTRING_PREFIX) + .append("is in live tile mode, trying to use overview input consumer")); } else if (gestureState.getRunningTask() == null) { - return getDefaultInputConsumer(); - } else if (previousGestureState.isRunningAnimationToLauncher() - || (gestureState.getActivityInterface().isResumed() - // with shell-transitions, home is resumed during recents animation, so - // explicitly check against recents animation too. - && !previousGestureState.isRecentsAnimationRunning()) + return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX) + .append("running task == null")); + } else if (previousGestureAnimatedToLauncher + || launcherResumedThroughShellTransition || forceOverviewInputConsumer) { return createOverviewInputConsumer( - previousGestureState, gestureState, event, forceOverviewInputConsumer); + previousGestureState, + gestureState, + event, + forceOverviewInputConsumer, + reasonString.append(SUBSTRING_PREFIX) + .append(previousGestureAnimatedToLauncher + ? "previous gesture animated to launcher" + : (launcherResumedThroughShellTransition + ? "launcher resumed through a shell transition" + : "forceOverviewInputConsumer == true")) + .append(", trying to use overview input consumer")); } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) { - return getDefaultInputConsumer(); + return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX) + .append("is gesture-blocked task, trying to use default input consumer")); } else { + reasonString.append(SUBSTRING_PREFIX) + .append("using OtherActivityInputConsumer"); return createOtherActivityInputConsumer(gestureState, event); } } @@ -853,21 +982,34 @@ public class TouchInteractionService extends Service mInputMonitorCompat, mInputEventReceiver, disableHorizontalSwipe, factory); } - private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) { + private InputConsumer createDeviceLockedInputConsumer( + GestureState gestureState, CompoundString reasonString) { if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) { - return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager, - gestureState, mInputMonitorCompat); + reasonString.append(SUBSTRING_PREFIX) + .append("device is in gesture nav mode and running task != null") + .append(", using DeviceLockedInputConsumer"); + return new DeviceLockedInputConsumer( + this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat); } else { - return getDefaultInputConsumer(); + return getDefaultInputConsumer(reasonString + .append(SUBSTRING_PREFIX) + .append(mDeviceState.isFullyGesturalNavMode() + ? "running task == null" : "device is not in gesture nav mode") + .append(", trying to use default input consumer")); } } - public InputConsumer createOverviewInputConsumer(GestureState previousGestureState, - GestureState gestureState, MotionEvent event, - boolean forceOverviewInputConsumer) { + public InputConsumer createOverviewInputConsumer( + GestureState previousGestureState, + GestureState gestureState, + MotionEvent event, + boolean forceOverviewInputConsumer, + CompoundString reasonString) { StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity(); if (activity == null) { - return getDefaultInputConsumer(); + return getDefaultInputConsumer( + reasonString.append(SUBSTRING_PREFIX) + .append("activity == null, trying to use default input consumer")); } if (activity.getRootView().hasWindowFocus() @@ -876,9 +1018,13 @@ public class TouchInteractionService extends Service && forceOverviewInputConsumer) || (ENABLE_QUICKSTEP_LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode())) { + reasonString.append(SUBSTRING_PREFIX) + .append("overview should have focus, using OverviewInputConsumer"); return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat, false /* startingInActivityBounds */); } else { + reasonString.append(SUBSTRING_PREFIX).append( + "overview shouldn't have focus, using OverviewWithoutFocusInputConsumer"); final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event); return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState, mInputMonitorCompat, disableHorizontalSwipe); @@ -906,13 +1052,21 @@ public class TouchInteractionService extends Service } } + private @NonNull InputConsumer getDefaultInputConsumer() { + return getDefaultInputConsumer(CompoundString.NO_OP); + } + /** * Returns the {@link ResetGestureInputConsumer} if user is unlocked, else NO_OP. */ - private @NonNull InputConsumer getDefaultInputConsumer() { + private @NonNull InputConsumer getDefaultInputConsumer(@NonNull CompoundString reasonString) { if (mResetGestureInputConsumer != null) { + reasonString.append(SUBSTRING_PREFIX).append( + "mResetGestureInputConsumer initialized, using ResetGestureInputConsumer"); return mResetGestureInputConsumer; } else { + reasonString.append(SUBSTRING_PREFIX).append( + "mResetGestureInputConsumer not initialized, using no-op input consumer"); // mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to // NO_OP until then (we never want these to be null). return InputConsumer.NO_OP; diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java index fabfc4bb51..be45f63ddb 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java @@ -15,15 +15,23 @@ */ package com.android.quickstep.util; -import android.content.Context; +import androidx.annotation.NonNull; -import com.android.launcher3.logging.EventLogArray; -import com.android.launcher3.util.MainThreadInitializedObject; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Objects; /** * A log to keep track of the active gesture. */ -public class ActiveGestureLog extends EventLogArray { +public class ActiveGestureLog { + + private static final int MAX_GESTURES_TRACKED = 10; public static final ActiveGestureLog INSTANCE = new ActiveGestureLog(); @@ -33,7 +41,238 @@ public class ActiveGestureLog extends EventLogArray { */ public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; + private static final int TYPE_ONE_OFF = 0; + private static final int TYPE_FLOAT = 1; + private static final int TYPE_INTEGER = 2; + private static final int TYPE_BOOL_TRUE = 3; + private static final int TYPE_BOOL_FALSE = 4; + private static final int TYPE_INPUT_CONSUMER = 5; + + private final EventLog[] logs; + private int nextIndex; + private int mCurrentLogId = 100; + private ActiveGestureLog() { - super("touch_interaction_log", 40); + this.logs = new EventLog[MAX_GESTURES_TRACKED]; + this.nextIndex = 0; + } + + public void addLog(String event) { + addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP); + } + + public void addLog(String event, int extras) { + addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP); + } + + public void addLog(String event, boolean extras) { + addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0, CompoundString.NO_OP); + } + + public void addLog(CompoundString compoundString) { + addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString); + } + + private void addLog( + int type, String event, float extras, @NonNull CompoundString compoundString) { + EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length]; + if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) { + EventLog eventLog = new EventLog(mCurrentLogId); + EventEntry eventEntry = new EventEntry(); + + eventEntry.update(type, event, extras, compoundString); + eventLog.eventEntries.add(eventEntry); + logs[nextIndex] = eventLog; + nextIndex = (nextIndex + 1) % logs.length; + return; + } + + // Update the last EventLog + List lastEventEntries = lastEventLog.eventEntries; + EventEntry lastEntry = lastEventEntries.size() > 0 + ? lastEventEntries.get(lastEventEntries.size() - 1) : null; + EventEntry secondLastEntry = lastEventEntries.size() > 1 + ? lastEventEntries.get(lastEventEntries.size() - 2) : null; + + // Update the last EventEntry if it's a duplicate + if (isEntrySame(lastEntry, type, event, compoundString) + && isEntrySame(secondLastEntry, type, event, compoundString)) { + lastEntry.update(type, event, extras, compoundString); + secondLastEntry.duplicateCount++; + return; + } + EventEntry eventEntry = new EventEntry(); + + eventEntry.update(type, event, extras, compoundString); + lastEventEntries.add(eventEntry); + } + + public void clear() { + Arrays.fill(logs, null); + } + + public void dump(String prefix, PrintWriter writer) { + writer.println(prefix + "ActiveGestureLog history:"); + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ ", Locale.US); + Date date = new Date(); + + for (int i = 0; i < logs.length; i++) { + EventLog eventLog = logs[(nextIndex + logs.length - i - 1) % logs.length]; + if (eventLog == null) { + continue; + } + writer.println(prefix + "\tLogs for logId: " + eventLog.logId); + + List eventEntries = eventLog.eventEntries; + for (int j = eventEntries.size() - 1; j >= 0; j--) { + EventEntry eventEntry = eventEntries.get(j); + date.setTime(eventEntry.time); + + StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date)) + .append(eventEntry.event); + switch (eventEntry.type) { + case TYPE_BOOL_FALSE: + msg.append(": false"); + break; + case TYPE_BOOL_TRUE: + msg.append(": true"); + break; + case TYPE_FLOAT: + msg.append(": ").append(eventEntry.extras); + break; + case TYPE_INTEGER: + msg.append(": ").append((int) eventEntry.extras); + break; + case TYPE_INPUT_CONSUMER: + msg.append(eventEntry.mCompoundString); + break; + default: // fall out + } + if (eventEntry.duplicateCount > 0) { + msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events"); + } + writer.println(msg); + } + } + } + + /** + * Increments and returns the current log ID. This should be used every time a new log trace + * is started. + */ + public int incrementLogId() { + return mCurrentLogId++; + } + + private boolean isEntrySame( + EventEntry entry, int type, String event, CompoundString compoundString) { + return entry != null + && entry.type == type + && entry.event.equals(event) + && entry.mCompoundString.equals(compoundString); + } + + /** A single event entry. */ + private static class EventEntry { + + private int type; + private String event; + private float extras; + @NonNull private CompoundString mCompoundString; + private long time; + private int duplicateCount; + + public void update( + int type, + String event, + float extras, + @NonNull CompoundString compoundString) { + this.type = type; + this.event = event; + this.extras = extras; + this.mCompoundString = compoundString; + time = System.currentTimeMillis(); + duplicateCount = 0; + } + } + + /** An entire log of entries associated with a single log ID */ + private static class EventLog { + + private final List eventEntries = new ArrayList<>(); + private final int logId; + + protected EventLog(int logId) { + this.logId = logId; + } + } + + /** A buildable string stored as an array for memory efficiency. */ + public static class CompoundString { + + public static final CompoundString NO_OP = new CompoundString(); + + private final List mSubstrings; + + private final boolean mIsNoOp; + + private CompoundString() { + this(null); + } + + public CompoundString(String substring) { + mIsNoOp = substring == null; + if (mIsNoOp) { + mSubstrings = null; + return; + } + mSubstrings = new ArrayList<>(); + mSubstrings.add(substring); + } + + public CompoundString append(CompoundString substring) { + if (mIsNoOp) { + return this; + } + mSubstrings.addAll(substring.mSubstrings); + + return this; + } + + public CompoundString append(String substring) { + if (mIsNoOp) { + return this; + } + mSubstrings.add(substring); + + return this; + } + + @Override + public String toString() { + if (mIsNoOp) { + return "ERROR: cannot use No-Op compound string"; + } + StringBuilder sb = new StringBuilder(); + for (String substring : mSubstrings) { + sb.append(substring); + } + + return sb.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(mIsNoOp, mSubstrings); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CompoundString)) { + return false; + } + CompoundString other = (CompoundString) obj; + return mIsNoOp && other.mIsNoOp && Objects.equals(mSubstrings, other.mSubstrings); + } } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index d0dbaf4ae5..616b08a908 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -75,6 +75,7 @@ import android.widget.LinearLayout; import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; @@ -925,4 +926,12 @@ public final class Utilities { } return options; } + + public static boolean bothNull(@Nullable Object a, @Nullable Object b) { + return a == null && b == null; + } + + public static boolean bothNonNull(@Nullable Object a, @Nullable Object b) { + return a != null && b != null; + } } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index dd58e710e4..49466adb38 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -63,6 +63,11 @@ public final class FeatureFlags { * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"), * and set a default value for the flag. This will be the default value on Debug builds. */ + public static final BooleanFlag ENABLE_INPUT_CONSUMER_REASON_LOGGING = getDebugFlag( + "ENABLE_INPUT_CONSUMER_REASON_LOGGING", + false, + "Log the reason why an Input Consumer was selected for a gesture."); + // When enabled the promise icon is visible in all apps while installation an app. public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag( "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps"); diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java deleted file mode 100644 index 3ecfb23c2c..0000000000 --- a/src/com/android/launcher3/logging/EventLogArray.java +++ /dev/null @@ -1,150 +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.logging; - - -import android.util.Log; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.Locale; -import java.util.Random; - -/** - * A utility class to record and log events. Events are stored in a fixed size array and old logs - * are purged as new events come. - */ -public class EventLogArray { - - private static final int TYPE_ONE_OFF = 0; - private static final int TYPE_FLOAT = 1; - private static final int TYPE_INTEGER = 2; - private static final int TYPE_BOOL_TRUE = 3; - private static final int TYPE_BOOL_FALSE = 4; - - private final String name; - private final EventEntry[] logs; - private int nextIndex; - private int mLogId; - - public EventLogArray(String name, int size) { - this.name = name; - logs = new EventEntry[size]; - nextIndex = 0; - } - - public void addLog(String event) { - addLog(TYPE_ONE_OFF, event, 0); - } - - public void addLog(String event, int extras) { - addLog(TYPE_INTEGER, event, extras); - } - - public void addLog(String event, boolean extras) { - addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0); - } - - private void addLog(int type, String event, float extras) { - // Merge the logs if its a duplicate - int last = (nextIndex + logs.length - 1) % logs.length; - int secondLast = (nextIndex + logs.length - 2) % logs.length; - if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) { - logs[last].update(type, event, extras, mLogId); - logs[secondLast].duplicateCount++; - return; - } - - if (logs[nextIndex] == null) { - logs[nextIndex] = new EventEntry(); - } - logs[nextIndex].update(type, event, extras, mLogId); - nextIndex = (nextIndex + 1) % logs.length; - } - - public void clear() { - Arrays.setAll(logs, (i) -> null); - } - - public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "EventLog (" + name + ") history:"); - SimpleDateFormat sdf = new SimpleDateFormat(" HH:mm:ss.SSSZ ", Locale.US); - Date date = new Date(); - - for (int i = 0; i < logs.length; i++) { - EventEntry log = logs[(nextIndex + logs.length - i - 1) % logs.length]; - if (log == null) { - continue; - } - date.setTime(log.time); - - StringBuilder msg = new StringBuilder(prefix).append(sdf.format(date)) - .append(log.event); - switch (log.type) { - case TYPE_BOOL_FALSE: - msg.append(": false"); - break; - case TYPE_BOOL_TRUE: - msg.append(": true"); - break; - case TYPE_FLOAT: - msg.append(": ").append(log.extras); - break; - case TYPE_INTEGER: - msg.append(": ").append((int) log.extras); - break; - default: // fall out - } - if (log.duplicateCount > 0) { - msg.append(" & ").append(log.duplicateCount).append(" similar events"); - } - msg.append(" traceId: ").append(log.traceId); - writer.println(msg); - } - } - - /** Returns a 3 digit random number between 100-999 */ - public int generateAndSetLogId() { - Random r = new Random(); - mLogId = r.nextInt(900) + 100; - return mLogId; - } - - private boolean isEntrySame(EventEntry entry, int type, String event) { - return entry != null && entry.type == type && entry.event.equals(event); - } - - /** A single event entry. */ - private static class EventEntry { - - private int type; - private String event; - private float extras; - private long time; - private int duplicateCount; - private int traceId; - - public void update(int type, String event, float extras, int traceId) { - this.type = type; - this.event = event; - this.extras = extras; - this.traceId = traceId; - time = System.currentTimeMillis(); - duplicateCount = 0; - } - } -}