Add input consumer selection reason to active gesture logs
Test: manually tried gestures in 3 button and gesture nav and checked logs
Bug: 227514916
Change-Id: I7a48d960ef5a41ddee95153fa08a2345f00cbef7
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c46926e..12a951f 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.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 @@
public class TouchInteractionService extends Service
implements ProtoTraceable<LauncherTraceProto.Builder> {
+ 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 @@
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 @@
// 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,12 +677,26 @@
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) {
@@ -699,50 +714,88 @@
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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
&& 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 @@
}
}
+ 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 064395f..bd96217 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -15,11 +15,16 @@
*/
package com.android.quickstep.util;
+import androidx.annotation.NonNull;
+
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;
import java.util.Random;
/**
@@ -40,6 +45,7 @@
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 String name;
private final EventEntry[] logs;
@@ -53,23 +59,29 @@
}
public void addLog(String event) {
- addLog(TYPE_ONE_OFF, event, 0);
+ addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP);
}
public void addLog(String event, int extras) {
- addLog(TYPE_INTEGER, event, 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);
+ addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0, CompoundString.NO_OP);
}
- private void addLog(int type, String event, float extras) {
+ public void addLog(CompoundString compoundString) {
+ addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString);
+ }
+
+ private void addLog(
+ int type, String event, float extras, @NonNull CompoundString compoundString) {
// Merge the logs if it's 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);
+ if (isEntrySame(logs[last], type, event, compoundString)
+ && isEntrySame(logs[secondLast], type, event, compoundString)) {
+ logs[last].update(type, event, extras, compoundString, mLogId);
logs[secondLast].duplicateCount++;
return;
}
@@ -77,7 +89,7 @@
if (logs[nextIndex] == null) {
logs[nextIndex] = new EventEntry();
}
- logs[nextIndex].update(type, event, extras, mLogId);
+ logs[nextIndex].update(type, event, extras, compoundString, mLogId);
nextIndex = (nextIndex + 1) % logs.length;
}
@@ -112,6 +124,9 @@
case TYPE_INTEGER:
msg.append(": ").append((int) log.extras);
break;
+ case TYPE_INPUT_CONSUMER:
+ msg.append(log.mCompoundString);
+ break;
default: // fall out
}
if (log.duplicateCount > 0) {
@@ -129,8 +144,12 @@
return mLogId;
}
- private boolean isEntrySame(EventEntry entry, int type, String event) {
- return entry != null && entry.type == type && entry.event.equals(event);
+ 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. */
@@ -139,17 +158,93 @@
private int type;
private String event;
private float extras;
+ @NonNull private CompoundString mCompoundString;
private long time;
private int duplicateCount;
private int traceId;
- public void update(int type, String event, float extras, int traceId) {
+ public void update(
+ int type,
+ String event,
+ float extras,
+ @NonNull CompoundString compoundString,
+ int traceId) {
this.type = type;
this.event = event;
this.extras = extras;
+ this.mCompoundString = compoundString;
this.traceId = traceId;
time = System.currentTimeMillis();
duplicateCount = 0;
}
}
+
+ /** 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<String> 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 d0dbaf4..616b08a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -75,6 +75,7 @@
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 @@
}
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 eabbf60..5bce524 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -63,6 +63,11 @@
* 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");