diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index e4968077db..2fd807d3c7 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -34,6 +34,9 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; +import com.android.launcher3.logger.LauncherAtom.FolderIcon; +import com.android.launcher3.logger.LauncherAtom.FromState; +import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.logging.StatsLogManager; @@ -174,6 +177,9 @@ public class StatsLogCompatManager extends StatsLogManager { private Optional mContainerInfo = Optional.empty(); private int mSrcState = LAUNCHER_UICHANGED__SRC_STATE__HOME; private int mDstState = LAUNCHER_UICHANGED__DST_STATE__BACKGROUND; + private Optional mFromState = Optional.empty(); + private Optional mToState = Optional.empty(); + private Optional mEditText = Optional.empty(); @Override public StatsLogger withItemInfo(ItemInfo itemInfo) { @@ -219,26 +225,34 @@ public class StatsLogCompatManager extends StatsLogManager { return this; } + @Override + public StatsLogger withFromState(FromState fromState) { + this.mFromState = Optional.of(fromState); + return this; + } + + @Override + public StatsLogger withToState(ToState toState) { + this.mToState = Optional.of(toState); + return this; + } + + @Override + public StatsLogger withEditText(String editText) { + this.mEditText = Optional.of(editText); + return this; + } + @Override public void log(EventEnum event) { if (!Utilities.ATLEAST_R) { return; } - LauncherAtom.ItemInfo.Builder itemInfoBuilder = - (LauncherAtom.ItemInfo.Builder) mItemInfo.buildProto().toBuilder(); - mRank.ifPresent(itemInfoBuilder::setRank); - if (mContainerInfo.isPresent()) { - // User already provided container info; - // default container info from item info will be ignored. - itemInfoBuilder.setContainerInfo(mContainerInfo.get()); - write(event, mInstanceId, itemInfoBuilder.build(), mSrcState, mDstState); - return; - } - if (mItemInfo.container < 0) { // Item is not within a folder. Write to StatsLog in same thread. - write(event, mInstanceId, itemInfoBuilder.build(), mSrcState, mDstState); + write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState, + mDstState); } else { // Item is inside the folder, fetch folder info in a BG thread // and then write to StatsLog. @@ -248,17 +262,33 @@ public class StatsLogCompatManager extends StatsLogManager { public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container); - LauncherAtom.ItemInfo.Builder atomInfoBuilder = - (LauncherAtom.ItemInfo.Builder) mItemInfo - .buildProto(folderInfo).toBuilder(); - mRank.ifPresent(atomInfoBuilder::setRank); - write(event, mInstanceId, atomInfoBuilder.build(), mSrcState, - mDstState); + write(event, mInstanceId, + applyOverwrites(mItemInfo.buildProto(folderInfo)), + mSrcState, mDstState); } }); } } + private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) { + LauncherAtom.ItemInfo.Builder itemInfoBuilder = + (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder(); + + mRank.ifPresent(itemInfoBuilder::setRank); + mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo); + + if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) { + FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder + .getFolderIcon() + .toBuilder(); + mFromState.ifPresent(folderIconBuilder::setFromLabelState); + mToState.ifPresent(folderIconBuilder::setToLabelState); + mEditText.ifPresent(folderIconBuilder::setLabelInfo); + itemInfoBuilder.setFolderIcon(folderIconBuilder); + } + return itemInfoBuilder.build(); + } + private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo, int srcState, int dstState) { if (IS_VERBOSE) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index fb58f21344..a8dca129dd 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -415,7 +415,7 @@ public class Workspace extends PagedView // Always enter the spring loaded mode mLauncher.getStateManager().goToState(SPRING_LOADED); - mStatsLogManager.logger().withItemInfo(dragObject.originalDragInfo) + mStatsLogManager.logger().withItemInfo(dragObject.dragInfo) .withInstanceId(dragObject.logInstanceId) .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED); } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 301f79c405..e950f3c1a0 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -81,7 +81,10 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.logger.LauncherAtom.FromState; +import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.FolderInfo.FolderListener; @@ -99,6 +102,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.StringJoiner; import java.util.stream.Collectors; /** @@ -109,6 +113,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener { private static final String TAG = "Launcher.Folder"; private static final boolean DEBUG = false; + + /** + * Used for separating folder title when logging together. + */ + private static final CharSequence FOLDER_LABEL_DELIMITER = "~"; + /** * We avoid measuring {@link #mContent} with a 0 width or height, as this * results in CellLayout being measured as UNSPECIFIED, which it does not support. @@ -155,6 +165,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo protected final Launcher mLauncher; protected DragController mDragController; public FolderInfo mInfo; + private CharSequence mFromTitle; + private FromState mFromLabelState; @Thunk FolderIcon mFolderIcon; @@ -335,7 +347,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo Log.d(TAG, "onBackKey newTitle=" + newTitle); } mInfo.setTitle(newTitle); - mInfo.fromCustom = mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(), mLauncher.getModelWriter()); mFolderIcon.onTitleChanged(newTitle); @@ -415,6 +426,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo void bind(FolderInfo info) { mInfo = info; + mFromTitle = info.title; + mFromLabelState = info.getFromLabelState(); ArrayList children = info.contents; Collections.sort(children, ITEM_POS_COMPARATOR); updateItemLocationsInDatabaseBatch(true); @@ -1442,10 +1455,38 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public void onFocusChange(View v, boolean hasFocus) { if (v == mFolderName) { if (hasFocus) { + mFromLabelState = mInfo.getFromLabelState(); + mFromTitle = mInfo.title; startEditingFolderName(); } else { - mStatsLogManager.logger().withItemInfo(mInfo).log(LAUNCHER_FOLDER_LABEL_UPDATED); - logFolderLabelState(); + StatsLogger statsLogger = mStatsLogManager.logger() + .withItemInfo(mInfo) + .withFromState(mFromLabelState); + + // If the folder label is suggested, it is logged to improve prediction model. + // When both old and new labels are logged together delimiter is used. + StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER); + if (mFromLabelState.equals(FromState.FROM_SUGGESTED)) { + labelInfoBuilder.add(mFromTitle); + } + + ToState toLabelState; + if (mFromTitle != null && mFromTitle.equals(mInfo.title)) { + toLabelState = ToState.UNCHANGED; + } else { + toLabelState = mInfo.getToLabelState(); + if (toLabelState.toString().startsWith("TO_SUGGESTION")) { + labelInfoBuilder.add(mInfo.title); + } + } + statsLogger.withToState(toLabelState); + + if (labelInfoBuilder.length() > 0) { + statsLogger.withEditText(labelInfoBuilder.toString()); + } + + statsLogger.log(LAUNCHER_FOLDER_LABEL_UPDATED); + logFolderLabelState(mFromLabelState, toLabelState); mFolderName.dispatchBackKey(); } } @@ -1650,8 +1691,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo * @deprecated This method is only used for log validation and soon will be removed. */ @Deprecated - public void logFolderLabelState() { + public void logFolderLabelState(FromState fromState, ToState toState) { mLauncher.getUserEventDispatcher() - .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent()); + .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent(fromState, toState)); } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 7af4664a05..ce37506385 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -20,7 +20,7 @@ import static android.text.TextUtils.isEmpty; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -63,6 +63,8 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.icons.DotRenderer; +import com.android.launcher3.logger.LauncherAtom.FromState; +import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppInfo; @@ -428,7 +430,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mPreviewItemManager.hidePreviewItem(finalIndex, false); mFolder.showItem(item); setLabelSuggestion(nameInfos, instanceId); - mFolder.logFolderLabelState(); invalidate(); }, DROP_IN_ANIMATION_DURATION); } @@ -447,12 +448,25 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) { return; } - mInfo.setTitle(nameInfos[0].getLabel()); - StatsLogManager.newInstance(getContext()).logger().withItemInfo(mInfo) - .withInstanceId(instanceId).log(LAUNCHER_FOLDER_LABEL_UPDATED); + CharSequence newTitle = nameInfos[0].getLabel(); + FromState fromState = mInfo.getFromLabelState(); + + mInfo.setTitle(newTitle); onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); + + // Logging for folder creation flow + StatsLogManager.newInstance(getContext()).logger() + .withInstanceId(instanceId) + .withItemInfo(mInfo) + .withFromState(fromState) + .withToState(ToState.TO_SUGGESTION0) + // When LAUNCHER_FOLDER_LABEL_UPDATED event.edit_text does not have delimiter, + // event is assumed to be folder creation on the server side. + .withEditText(newTitle.toString()) + .log(LAUNCHER_FOLDER_AUTO_LABELED); + mFolder.logFolderLabelState(fromState, ToState.TO_SUGGESTION0); } diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 82d61dac07..be8edc715b 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -21,6 +21,8 @@ import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; +import com.android.launcher3.logger.LauncherAtom.FromState; +import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.ResourceBasedOverride; @@ -66,8 +68,10 @@ public class StatsLogManager implements ResourceBasedOverride { + "resulting in a new folder creation") LAUNCHER_ITEM_DROP_FOLDER_CREATED(386), - @UiEvent(doc = "User action resulted in or manually updated the folder label to " - + "new/same value.") + @UiEvent(doc = "Folder's label is automatically assigned.") + LAUNCHER_FOLDER_AUTO_LABELED(591), + + @UiEvent(doc = "User manually updated the folder label.") LAUNCHER_FOLDER_LABEL_UPDATED(460), @UiEvent(doc = "User long pressed on the workspace empty space.") @@ -245,6 +249,27 @@ public class StatsLogManager implements ResourceBasedOverride { return this; } + /** + * Sets FromState field of log message. + */ + default StatsLogger withFromState(FromState fromState) { + return this; + } + + /** + * Sets ToState field of log message. + */ + default StatsLogger withToState(ToState toState) { + return this; + } + + /** + * Sets editText field of log message. + */ + default StatsLogger withEditText(String editText) { + return this; + } + /** * Sets the final value for container related fields of log message. * diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java index 8f577b59e0..08eb3831a0 100644 --- a/src/com/android/launcher3/model/data/FolderInfo.java +++ b/src/com/android/launcher3/model/data/FolderInfo.java @@ -54,7 +54,6 @@ import java.util.ArrayList; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; -import java.util.StringJoiner; import java.util.stream.IntStream; @@ -88,20 +87,6 @@ public class FolderInfo extends ItemInfo { public Intent suggestedFolderNames; - // Represents the title before current. - // Primarily used for logging purpose. - private CharSequence mPreviousTitle; - - // True if the title before was manually entered, suggested otherwise. - // Primarily used for logging purpose. - public boolean fromCustom; - - /** - * Used for separating {@link #mPreviousTitle} and {@link #title} when concatenating them - * for logging. - */ - private static final CharSequence FOLDER_LABEL_DELIMITER = "=>"; - /** * The apps and shortcuts */ @@ -207,21 +192,16 @@ public class FolderInfo extends ItemInfo { return getDefaultItemInfoBuilder() .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size())) .setRank(rank) - .setAttribute(fromCustom ? MANUAL_LABEL : SUGGESTED_LABEL) + .setAttribute(hasOption(FLAG_MANUAL_FOLDER_NAME) ? MANUAL_LABEL : SUGGESTED_LABEL) .setContainerInfo(getContainerInfo()) .build(); } @Override public void setTitle(CharSequence title) { - mPreviousTitle = this.title; this.title = title; } - public CharSequence getPreviousTitle() { - return mPreviousTitle; - } - @Override public ItemInfo makeShallowCopy() { FolderInfo folderInfo = new FolderInfo(); @@ -235,30 +215,7 @@ public class FolderInfo extends ItemInfo { */ @Override public LauncherAtom.ItemInfo buildProto() { - FromState fromFolderLabelState = getFromFolderLabelState(); - ToState toFolderLabelState = getToFolderLabelState(); - LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder() - .setCardinality(contents.size()) - .setFromLabelState(fromFolderLabelState) - .setToLabelState(toFolderLabelState); - - // If the folder label is suggested, it is logged to improve prediction model. - // When both old and new labels are logged together delimiter is used. - StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER); - if (fromFolderLabelState.equals(FromState.FROM_SUGGESTED)) { - labelInfoBuilder.add(mPreviousTitle); - } - if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) { - labelInfoBuilder.add(title); - } - if (labelInfoBuilder.length() > 0) { - folderIconBuilder.setLabelInfo(labelInfoBuilder.toString()); - } - - return getDefaultItemInfoBuilder() - .setFolderIcon(folderIconBuilder) - .setContainerInfo(getContainerInfo()) - .build(); + return buildProto(null); } /** @@ -280,15 +237,27 @@ public class FolderInfo extends ItemInfo { } - private LauncherAtom.ToState getToFolderLabelState() { + /** + * Returns {@link FromState} based on current {@link #title}. + */ + public LauncherAtom.FromState getFromLabelState() { + return title == null + ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED + : title.length() == 0 + ? LauncherAtom.FromState.FROM_EMPTY + : hasOption(FLAG_MANUAL_FOLDER_NAME) + ? LauncherAtom.FromState.FROM_CUSTOM + : LauncherAtom.FromState.FROM_SUGGESTED; + } + + /** + * Returns {@link ToState} based on current {@link #title}. + */ + public LauncherAtom.ToState getToLabelState() { if (title == null) { return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; } - if (title.equals(mPreviousTitle)) { - return LauncherAtom.ToState.UNCHANGED; - } - if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { return title.length() > 0 ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED @@ -335,17 +304,6 @@ public class FolderInfo extends ItemInfo { // fall through } return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; - - } - - private LauncherAtom.FromState getFromFolderLabelState() { - return mPreviousTitle == null - ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED - : mPreviousTitle.length() == 0 - ? LauncherAtom.FromState.FROM_EMPTY - : fromCustom - ? LauncherAtom.FromState.FROM_CUSTOM - : LauncherAtom.FromState.FROM_SUGGESTED; } private Optional getSuggestedLabels() { @@ -368,7 +326,8 @@ public class FolderInfo extends ItemInfo { * @deprecated This method is used only for validation purpose and soon will be removed. */ @Deprecated - public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent() { + public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent(FromState fromState, + ToState toState) { return LauncherLogProto.LauncherEvent.newBuilder() .setAction(LauncherLogProto.Action .newBuilder() @@ -377,8 +336,8 @@ public class FolderInfo extends ItemInfo { .newBuilder() .setType(Target.Type.ITEM) .setItemType(LauncherLogProto.ItemType.EDITTEXT) - .setFromFolderLabelState(convertFolderLabelState(getFromFolderLabelState())) - .setToFolderLabelState(convertFolderLabelState(getToFolderLabelState()))) + .setFromFolderLabelState(convertFolderLabelState(fromState)) + .setToFolderLabelState(convertFolderLabelState(toState))) .addSrcTarget(Target.newBuilder() .setType(Target.Type.CONTAINER) .setContainerType(LauncherLogProto.ContainerType.FOLDER)